From e2968af88ca78e3c5a5d829cd8eaf31742cb5a79 Mon Sep 17 00:00:00 2001
From: Chuck Atkins <chuck.atkins@kitware.com>
Date: Tue, 18 Apr 2017 17:09:30 -0400
Subject: [PATCH] Use relative includes for current directory

---
 examples/hello/timeBP/timeBPWriter_nompi.cpp  |     5 +-
 include/core/Engine.h                         |    12 +-
 include/core/Selection.h                      |     2 +-
 include/core/Support.h                        |    41 +-
 include/core/Variable.h                       |     4 +-
 include/core/VariableBase.h                   |     6 +-
 include/core/VariableCompound.h               |     2 +-
 .../external/{external => }/adios_selection.h |     0
 source/external/json.hpp                      | 20998 ++++++++--------
 9 files changed, 10614 insertions(+), 10456 deletions(-)
 rename source/external/{external => }/adios_selection.h (100%)

diff --git a/examples/hello/timeBP/timeBPWriter_nompi.cpp b/examples/hello/timeBP/timeBPWriter_nompi.cpp
index 65bd8a65e..3b8735fa6 100644
--- a/examples/hello/timeBP/timeBPWriter_nompi.cpp
+++ b/examples/hello/timeBP/timeBPWriter_nompi.cpp
@@ -57,7 +57,7 @@ int main(int /*argc*/, char ** /*argv*/)
         bpWriterSettings.AddTransport("File", "profile_units=mus",
                                       "have_metadata_file=no");
 
-        auto bpWriter = adios.Open("time_nompi.bp", "w",bpWriterSettings);
+        auto bpWriter = adios.Open("time_nompi.bp", "w", bpWriterSettings);
 
         for (unsigned int t = 0; t < 3; ++t)
         {
@@ -65,7 +65,8 @@ int main(int /*argc*/, char ** /*argv*/)
             myMatrix[0] = t;
             myMatrix2[0] = t;
 
-            // Base class Engine own the Write<T> that will call overloaded Write from Derived
+            // Base class Engine own the Write<T> that will call overloaded
+            // Write from Derived
             bpWriter->Write(ioMyDoubles, myDoubles.data());
             bpWriter->Write(ioMyMatrix, myMatrix.data());
             bpWriter->Write(ioMyMatrix2, myMatrix2.data());
diff --git a/include/core/Engine.h b/include/core/Engine.h
index 31fce6ca8..4954e81b8 100644
--- a/include/core/Engine.h
+++ b/include/core/Engine.h
@@ -25,12 +25,12 @@
 
 #include "ADIOS.h"
 #include "ADIOSTypes.h"
-#include "core/Capsule.h"
-#include "core/Method.h"
-#include "core/Transform.h"
-#include "core/Transport.h"
-#include "core/Variable.h"
-#include "core/VariableCompound.h"
+#include "Capsule.h"
+#include "Method.h"
+#include "Transform.h"
+#include "Transport.h"
+#include "Variable.h"
+#include "VariableCompound.h"
 
 namespace adios
 {
diff --git a/include/core/Selection.h b/include/core/Selection.h
index e138ac80d..187ea0eda 100644
--- a/include/core/Selection.h
+++ b/include/core/Selection.h
@@ -42,7 +42,7 @@ enum class SelectionType
     // Contiguous block of data defined by offsets and counts in each
     // dimension
     BoundingBox,
- 
+
     // List of individual points
     Points,
 
diff --git a/include/core/Support.h b/include/core/Support.h
index a36476660..6284f52cb 100644
--- a/include/core/Support.h
+++ b/include/core/Support.h
@@ -12,7 +12,6 @@
 #define SUPPORT_H_
 
 /// \cond EXCLUDE_FROM_DOXYGEN
-#include <array>
 #include <map>
 #include <set>
 #include <string>
@@ -23,25 +22,29 @@ namespace adios
 
 struct Support
 {
-    static const std::string Version;                 ///< current ADIOS version
-    static const std::set<std::string> HostLanguages; ///< supported languages:
-                                                      /// C, C++, Fortran,
-    /// Python, Java
+    ///< current ADIOS version
+    static const std::string Version;
+
+    ///< supported languages: C, C++, Fortran, Python, Java
+    static const std::set<std::string> HostLanguages;
+
     static const std::set<std::string> Numbers;
-    static const std::set<std::string>
-        Transports; ///< supported transport methods
-    static const std::set<std::string>
-        Transforms; ///< supported data transform methods
-    static const std::map<std::string, std::set<std::string>>
-        Datatypes; ///< supported data types, key: host language, value: all
-                   /// supported types
-    static const std::map<std::string, std::set<std::string>>
-        DatatypesAliases; ///< all supported int aliases, key: C++ type
-                          ///(e.g.
-                          /// int), value: aliases to type in key (e.g. int,
-    /// integer)
-
-    static const std::set<std::string> FileTransports; ///< file I/O transports
+
+    ///< supported transport methods
+    static const std::set<std::string> Transports;
+
+    ///< supported data transform methods
+    static const std::set<std::string> Transforms;
+
+    ///< supported data types, key: host language, value: all supported types
+    static const std::map<std::string, std::set<std::string>> Datatypes;
+
+    ///< all supported int aliases, key: C++ type (e.g. int), value: aliases to
+    /// type in key (e.g. int, integer)
+    static const std::map<std::string, std::set<std::string>> DatatypesAliases;
+
+    ///< file I/O transports
+    static const std::set<std::string> FileTransports;
 
     enum class Resolutions
     {
diff --git a/include/core/Variable.h b/include/core/Variable.h
index ca91c790f..be5fc5675 100644
--- a/include/core/Variable.h
+++ b/include/core/Variable.h
@@ -18,8 +18,8 @@
 #include <vector>
 /// \endcond
 
-#include "core/Transform.h"
-#include "core/VariableBase.h"
+#include "Transform.h"
+#include "VariableBase.h"
 
 namespace adios
 {
diff --git a/include/core/VariableBase.h b/include/core/VariableBase.h
index e4d1a63d7..1d7270699 100644
--- a/include/core/VariableBase.h
+++ b/include/core/VariableBase.h
@@ -19,9 +19,9 @@
 #include <vector>
 /// \endcond
 
-#include "functions/adiosFunctions.h" //GetTotalSize, DimsToCSV, ConvertUint64VectorToSizetVector
-#include "functions/adiosTemplates.h"       //GetType<T>
-#include "SelectionBoundingBox.h" //Selection
+#include "SelectionBoundingBox.h"
+#include "functions/adiosFunctions.h"
+#include "functions/adiosTemplates.h"
 
 namespace adios
 {
diff --git a/include/core/VariableCompound.h b/include/core/VariableCompound.h
index 322dccd4f..9f3ceeebe 100644
--- a/include/core/VariableCompound.h
+++ b/include/core/VariableCompound.h
@@ -11,7 +11,7 @@
 #ifndef VARIABLECOMPOUND_H_
 #define VARIABLECOMPOUND_H_
 
-#include "core/VariableBase.h"
+#include "VariableBase.h"
 
 namespace adios
 {
diff --git a/source/external/external/adios_selection.h b/source/external/adios_selection.h
similarity index 100%
rename from source/external/external/adios_selection.h
rename to source/external/adios_selection.h
diff --git a/source/external/json.hpp b/source/external/json.hpp
index c919f46b7..2f277300c 100644
--- a/source/external/json.hpp
+++ b/source/external/json.hpp
@@ -126,17 +126,18 @@ contains a `mapped_type`, whereas `std::vector` fails the test.
 @sa http://stackoverflow.com/a/7728728/266378
 @since version 1.0.0, overworked in version 2.0.6
 */
-template <typename T> struct has_mapped_type
+template <typename T>
+struct has_mapped_type
 {
 private:
-  template <typename U, typename = typename U::mapped_type>
-  static int detect(U &&);
+    template <typename U, typename = typename U::mapped_type>
+    static int detect(U &&);
 
-  static void detect(...);
+    static void detect(...);
 
 public:
-  static constexpr bool value =
-      std::is_integral<decltype(detect(std::declval<T>()))>::value;
+    static constexpr bool value =
+        std::is_integral<decltype(detect(std::declval<T>()))>::value;
 };
 
 } // namespace
@@ -239,12060 +240,12212 @@ template <template <typename U, typename V, typename... Args> class ObjectType =
 class basic_json
 {
 private:
-  /// workaround type for MSVC
-  using basic_json_t =
-      basic_json<ObjectType, ArrayType, StringType, BooleanType,
-                 NumberIntegerType, NumberUnsignedType, NumberFloatType,
-                 AllocatorType>;
+    /// workaround type for MSVC
+    using basic_json_t =
+        basic_json<ObjectType, ArrayType, StringType, BooleanType,
+                   NumberIntegerType, NumberUnsignedType, NumberFloatType,
+                   AllocatorType>;
 
 public:
-  // forward declarations
-  template <typename U> class iter_impl;
-  template <typename Base> class json_reverse_iterator;
-  class json_pointer;
-
-  /////////////////////
-  // container types //
-  /////////////////////
-
-  /// @name container types
-  /// The canonic container types to use @ref basic_json like any other STL
-  /// container.
-  /// @{
-
-  /// the type of elements in a basic_json container
-  using value_type = basic_json;
-
-  /// the type of an element reference
-  using reference = value_type &;
-  /// the type of an element const reference
-  using const_reference = const value_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;
-
-  /// the allocator type
-  using allocator_type = AllocatorType<basic_json>;
-
-  /// 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;
-
-  /// 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 returns the allocator associated with the container
-  */
-  static allocator_type get_allocator() { return allocator_type(); }
-
-  /*!
-  @brief returns version information on the library
-  */
-  static basic_json meta()
-  {
-    basic_json result;
-
-    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},
-    };
+    // forward declarations
+    template <typename U>
+    class iter_impl;
+    template <typename Base>
+    class json_reverse_iterator;
+    class json_pointer;
+
+    /////////////////////
+    // container types //
+    /////////////////////
+
+    /// @name container types
+    /// The canonic container types to use @ref basic_json like any other STL
+    /// container.
+    /// @{
+
+    /// the type of elements in a basic_json container
+    using value_type = basic_json;
+
+    /// the type of an element reference
+    using reference = value_type &;
+    /// the type of an element const reference
+    using const_reference = const value_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;
+
+    /// the allocator type
+    using allocator_type = AllocatorType<basic_json>;
+
+    /// 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;
+
+    /// 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 returns the allocator associated with the container
+    */
+    static allocator_type get_allocator() { return allocator_type(); }
+
+    /*!
+    @brief returns version information on the library
+    */
+    static basic_json meta()
+    {
+        basic_json result;
+
+        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},
+        };
 
 #ifdef _WIN32
-    result["platform"] = "win32";
+        result["platform"] = "win32";
 #elif defined __linux__
-    result["platform"] = "linux";
+        result["platform"] = "linux";
 #elif defined __APPLE__
-    result["platform"] = "apple";
+        result["platform"] = "apple";
 #elif defined __unix__
-    result["platform"] = "unix";
+        result["platform"] = "unix";
 #else
-    result["platform"] = "unknown";
+        result["platform"] = "unknown";
 #endif
 
 #if defined(__clang__)
-    result["compiler"] = {{"family", "clang"}, {"version", __clang_version__}};
+        result["compiler"] = {{"family", "clang"},
+                              {"version", __clang_version__}};
 #elif defined(__ICC) || defined(__INTEL_COMPILER)
-    result["compiler"] = {{"family", "icc"}, {"version", __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__)}};
+        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"
+        result["compiler"] = "hp"
 #elif defined(__IBMCPP__)
-    result["compiler"] = {{"family", "ilecpp"}, {"version", __IBMCPP__}};
+        result["compiler"] = {{"family", "ilecpp"}, {"version", __IBMCPP__}};
 #elif defined(_MSC_VER)
-    result["compiler"] = {{"family", "msvc"}, {"version", _MSC_VER}};
+        result["compiler"] = {{"family", "msvc"}, {"version", _MSC_VER}};
 #elif defined(__PGI)
-    result["compiler"] = {{"family", "pgcpp"}, {"version", __PGI}};
+        result["compiler"] = {{"family", "pgcpp"}, {"version", __PGI}};
 #elif defined(__SUNPRO_CC)
-    result["compiler"] = {{"family", "sunpro"}, {"version", __SUNPRO_CC}};
+        result["compiler"] = {{"family", "sunpro"}, {"version", __SUNPRO_CC}};
 #else
-    result["compiler"] = {{"family", "unknown"}, {"version", "unknown"}};
+        result["compiler"] = {{"family", "unknown"}, {"version", "unknown"}};
 #endif
 
 #ifdef __cplusplus
-    result["compiler"]["c++"] = std::to_string(__cplusplus);
+        result["compiler"]["c++"] = std::to_string(__cplusplus);
 #else
-    result["compiler"]["c++"] = "unknown";
+        result["compiler"]["c++"] = "unknown";
 #endif
-    return result;
-  }
-
-  ///////////////////////////
-  // JSON value data types //
-  ///////////////////////////
-
-  /// @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.
-  /// @{
-
-  /*!
-  @brief a type for an object
-
-  [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.
-
-  To store objects in C++, a type is defined by the template parameters
-  described below.
-
-  @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`)
-
-  #### Default type
-
-  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:
-
-  @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
-
-  #### Behavior
-
-  The choice of @a object_t influences the behavior of the JSON class. With
-  the default type, objects have the following behavior:
-
-  - 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.
-
-  #### Limits
-
-  [RFC 7159](http://rfc7159.net/rfc7159) specifies:
-  > An implementation may set limits on the maximum depth of nesting.
-
-  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.
+        return result;
+    }
 
-  #### Storage
+    ///////////////////////////
+    // JSON value data types //
+    ///////////////////////////
 
-  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.
+    /// @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.
+    /// @{
 
-  @sa @ref array_t -- type for an array value
+    /*!
+    @brief a type for an object
 
-  @since version 1.0.0
+    [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.
 
-  @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>>>;
+    To store objects in C++, a type is defined by the template parameters
+    described below.
 
-  /*!
-  @brief a type for an array
+    @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`)
 
-  [RFC 7159](http://rfc7159.net/rfc7159) describes JSON arrays as follows:
-  > An array is an ordered sequence of zero or more values.
+    #### Default type
 
-  To store objects in C++, a type is defined by the template parameters
-  explained below.
+    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:
 
-  @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`)
+    @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
 
-  #### Default type
+    #### Behavior
+
+    The choice of @a object_t influences the behavior of the JSON class. With
+    the default type, objects have the following behavior:
+
+    - 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.
+
+    #### Limits
+
+    [RFC 7159](http://rfc7159.net/rfc7159) specifies:
+    > An implementation may set limits on the maximum depth of nesting.
+
+    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.
+
+    #### Storage
+
+    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.
+
+    @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.
+    */
+    using object_t =
+        ObjectType<StringType, basic_json, std::less<StringType>,
+                   AllocatorType<std::pair<const StringType, basic_json>>>;
 
-  With the default values for @a ArrayType (`std::vector`) and @a
-  AllocatorType (`std::allocator`), the default value for @a array_t is:
+    /*!
+    @brief a type for an array
 
-  @code {.cpp}
-  std::vector<
-    basic_json, // value_type
-    std::allocator<basic_json> // allocator_type
-  >
-  @endcode
+    [RFC 7159](http://rfc7159.net/rfc7159) describes JSON arrays as follows:
+    > An array is an ordered sequence of zero or more values.
 
-  #### Limits
+    To store objects in C++, a type is defined by the template parameters
+    explained below.
 
-  [RFC 7159](http://rfc7159.net/rfc7159) specifies:
-  > An implementation may set limits on the maximum depth of nesting.
+    @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`)
 
-  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.
+    #### Default type
 
-  #### Storage
+    With the default values for @a ArrayType (`std::vector`) and @a
+    AllocatorType (`std::allocator`), the default value for @a array_t is:
 
-  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.
+    @code {.cpp}
+    std::vector<
+      basic_json, // value_type
+      std::allocator<basic_json> // allocator_type
+    >
+    @endcode
 
-  @sa @ref object_t -- type for an object value
+    #### Limits
 
-  @since version 1.0.0
-  */
-  using array_t = ArrayType<basic_json, AllocatorType<basic_json>>;
+    [RFC 7159](http://rfc7159.net/rfc7159) specifies:
+    > An implementation may set limits on the maximum depth of nesting.
 
-  /*!
-  @brief a type for a string
+    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.
 
-  [RFC 7159](http://rfc7159.net/rfc7159) describes JSON strings as follows:
-  > A string is a sequence of zero or more Unicode characters.
+    #### Storage
 
-  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.
+    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.
 
-  @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.
+    @sa @ref object_t -- type for an object value
 
-  #### Default type
+    @since version 1.0.0
+    */
+    using array_t = ArrayType<basic_json, AllocatorType<basic_json>>;
 
-  With the default values for @a StringType (`std::string`), the default
-  value for @a string_t is:
+    /*!
+    @brief a type for a string
 
-  @code {.cpp}
-  std::string
-  @endcode
+    [RFC 7159](http://rfc7159.net/rfc7159) describes JSON strings as follows:
+    > A string is a sequence of zero or more Unicode characters.
 
-  #### Encoding
+    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.
 
-  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.
+    @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.
 
-  #### String comparison
+    #### Default type
 
-  [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.
+    With the default values for @a StringType (`std::string`), the default
+    value for @a string_t is:
 
-  This implementation is interoperable as it does compare strings code unit
-  by code unit.
+    @code {.cpp}
+    std::string
+    @endcode
 
-  #### Storage
+    #### Encoding
 
-  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.
+    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.
 
-  @since version 1.0.0
-  */
-  using string_t = StringType;
+    #### String comparison
 
-  /*!
-  @brief a type for a boolean
+    [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.
 
-  [RFC 7159](http://rfc7159.net/rfc7159) implicitly describes a boolean as a
-  type which differentiates the two literals `true` and `false`.
+    This implementation is interoperable as it does compare strings code unit
+    by code unit.
 
-  To store objects in C++, a type is defined by the template parameter @a
-  BooleanType which chooses the type to use.
+    #### Storage
 
-  #### Default type
+    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.
 
-  With the default values for @a BooleanType (`bool`), the default value for
-  @a boolean_t is:
+    @since version 1.0.0
+    */
+    using string_t = StringType;
 
-  @code {.cpp}
-  bool
-  @endcode
+    /*!
+    @brief a type for a boolean
 
-  #### Storage
+    [RFC 7159](http://rfc7159.net/rfc7159) implicitly describes a boolean as a
+    type which differentiates the two literals `true` and `false`.
 
-  Boolean values are stored directly inside a @ref basic_json type.
+    To store objects in C++, a type is defined by the template parameter @a
+    BooleanType which chooses the type to use.
 
-  @since version 1.0.0
-  */
-  using boolean_t = BooleanType;
+    #### Default type
 
-  /*!
-  @brief a type for a number (integer)
+    With the default values for @a BooleanType (`bool`), the default value for
+    @a boolean_t is:
 
-  [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.
+    @code {.cpp}
+    bool
+    @endcode
 
-  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.
+    #### Storage
 
-  To store integer numbers in C++, a type is defined by the template
-  parameter @a NumberIntegerType which chooses the type to use.
+    Boolean values are stored directly inside a @ref basic_json type.
 
-  #### Default type
+    @since version 1.0.0
+    */
+    using boolean_t = BooleanType;
 
-  With the default values for @a NumberIntegerType (`int64_t`), the default
-  value for @a number_integer_t is:
+    /*!
+    @brief a type for a number (integer)
 
-  @code {.cpp}
-  int64_t
-  @endcode
+    [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.
 
-  #### Default behavior
+    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.
 
-  - 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`.
+    To store integer numbers in C++, a type is defined by the template
+    parameter @a NumberIntegerType which chooses the type to use.
 
-  #### Limits
+    #### Default type
 
-  [RFC 7159](http://rfc7159.net/rfc7159) specifies:
-  > An implementation may set limits on the range and precision of numbers.
+    With the default values for @a NumberIntegerType (`int64_t`), the default
+    value for @a number_integer_t is:
 
-  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.
+    @code {.cpp}
+    int64_t
+    @endcode
 
-  [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.
+    #### Default behavior
 
-  As this range is a subrange of the exactly supported range [INT64_MIN,
-  INT64_MAX], this class's integer type is interoperable.
+    - 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`.
 
-  #### Storage
+    #### Limits
 
-  Integer number values are stored directly inside a @ref basic_json type.
+    [RFC 7159](http://rfc7159.net/rfc7159) specifies:
+    > An implementation may set limits on the range and precision of numbers.
 
-  @sa @ref number_float_t -- type for number values (floating-point)
+    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.
 
-  @sa @ref number_unsigned_t -- type for number values (unsigned integer)
+    [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.
 
-  @since version 1.0.0
-  */
-  using number_integer_t = NumberIntegerType;
+    As this range is a subrange of the exactly supported range [INT64_MIN,
+    INT64_MAX], this class's integer type is interoperable.
 
-  /*!
-  @brief a type for a number (unsigned)
+    #### Storage
 
-  [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.
+    Integer number values are stored directly inside a @ref basic_json type.
 
-  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.
+    @sa @ref number_float_t -- type for number values (floating-point)
 
-  To store unsigned integer numbers in C++, a type is defined by the
-  template parameter @a NumberUnsignedType which chooses the type to use.
+    @sa @ref number_unsigned_t -- type for number values (unsigned integer)
 
-  #### Default type
+    @since version 1.0.0
+    */
+    using number_integer_t = NumberIntegerType;
 
-  With the default values for @a NumberUnsignedType (`uint64_t`), the
-  default value for @a number_unsigned_t is:
+    /*!
+    @brief a type for a number (unsigned)
 
-  @code {.cpp}
-  uint64_t
-  @endcode
+    [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.
 
-  #### Default behavior
+    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.
 
-  - 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`.
+    To store unsigned integer numbers in C++, a type is defined by the
+    template parameter @a NumberUnsignedType which chooses the type to use.
 
-  #### Limits
+    #### Default type
 
-  [RFC 7159](http://rfc7159.net/rfc7159) specifies:
-  > An implementation may set limits on the range and precision of numbers.
+    With the default values for @a NumberUnsignedType (`uint64_t`), the
+    default value for @a number_unsigned_t is:
 
-  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.
+    @code {.cpp}
+    uint64_t
+    @endcode
 
-  [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.
+    #### Default behavior
 
-  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.
+    - 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`.
 
-  #### Storage
+    #### Limits
 
-  Integer number values are stored directly inside a @ref basic_json type.
+    [RFC 7159](http://rfc7159.net/rfc7159) specifies:
+    > An implementation may set limits on the range and precision of numbers.
 
-  @sa @ref number_float_t -- type for number values (floating-point)
-  @sa @ref number_integer_t -- type for number values (integer)
+    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.
 
-  @since version 2.0.0
-  */
-  using number_unsigned_t = NumberUnsignedType;
+    [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.
 
-  /*!
-  @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.
+    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.
 
-  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 floating-point numbers in C++, a type is defined by the template
-  parameter @a NumberFloatType which chooses the type to use.
-
-  #### Default type
-
-  With the default values for @a NumberFloatType (`double`), the default
-  value for @a number_float_t is:
-
-  @code {.cpp}
-  double
-  @endcode
-
-  #### Default behavior
-
-  - 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
-
-  [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.
-
-  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`.
-
-  #### Storage
-
-  Floating-point number values are stored directly inside a @ref basic_json
-  type.
-
-  @sa @ref number_integer_t -- type for number values (integer)
-
-  @sa @ref number_unsigned_t -- type for number values (unsigned integer)
-
-  @since version 1.0.0
-  */
-  using number_float_t = NumberFloatType;
-
-  /// @}
-
-  ///////////////////////////
-  // JSON type enumeration //
-  ///////////////////////////
-
-  /*!
-  @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 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.
-
-  @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.
-
-  @sa @ref 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
-  };
+    #### Storage
 
-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();
-  }
-
-  ////////////////////////
-  // JSON value storage //
-  ////////////////////////
-
-  /*!
-  @brief a JSON value
-
-  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.
-
-  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*
-
-  @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
-  */
-  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;
-
-    /// 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;
-        }
+    Integer number values are stored directly inside a @ref basic_json type.
 
-        case value_t::array:
-        {
-          array = create<array_t>();
-          break;
-        }
+    @sa @ref number_float_t -- type for number values (floating-point)
+    @sa @ref number_integer_t -- type for number values (integer)
 
-        case value_t::string:
-        {
-          string = create<string_t>("");
-          break;
-        }
+    @since version 2.0.0
+    */
+    using number_unsigned_t = NumberUnsignedType;
 
-        case value_t::boolean:
-        {
-          boolean = boolean_t(false);
-          break;
-        }
+    /*!
+    @brief a type for a number (floating-point)
 
-        case value_t::number_integer:
-        {
-          number_integer = number_integer_t(0);
-          break;
-        }
+    [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.
 
-        case value_t::number_unsigned:
-        {
-          number_unsigned = number_unsigned_t(0);
-          break;
-        }
+    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.
 
-        case value_t::number_float:
-        {
-          number_float = number_float_t(0.0);
-          break;
-        }
+    To store floating-point numbers in C++, a type is defined by the template
+    parameter @a NumberFloatType which chooses the type to use.
 
-        case value_t::null:
-        {
-          break;
-        }
+    #### Default type
 
-        default:
-        {
-          if (t == value_t::null)
-          {
-            JSON_THROW(std::domain_error("961c151d2e87f2686a955a9be24d316f1362b"
-                                         "f21 2.0.10")); // LCOV_EXCL_LINE
-          }
-          break;
-        }
-      }
-    }
+    With the default values for @a NumberFloatType (`double`), the default
+    value for @a number_float_t is:
 
-    /// constructor for strings
-    json_value(const string_t &value) { string = create<string_t>(value); }
+    @code {.cpp}
+    double
+    @endcode
 
-    /// constructor for objects
-    json_value(const object_t &value) { object = create<object_t>(value); }
+    #### Default behavior
 
-    /// constructor for arrays
-    json_value(const array_t &value) { array = create<array_t>(value); }
-  };
+    - 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 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) 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.
 
-public:
-  //////////////////////////
-  // JSON parser callback //
-  //////////////////////////
-
-  /*!
-  @brief JSON callback events
-
-  This enumeration lists the parser events that can trigger calling a
-  callback function of type @ref parser_callback_t during parsing.
-
-  @image html callback_events.png "Example when certain parse events are
-  triggered"
-
-  @since version 1.0.0
-  */
-  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
-  };
-
-  /*!
-  @brief per-element parser callback type
-
-  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.
-
-  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.
-
-  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
-
-  @image html callback_events.png "Example when certain parse events are
-  triggered"
-
-  Discarding a value (i.e., returning `false`) has different effects
-  depending on the context in which function was called:
-
-  - 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.
-
-  @param[in] depth  the depth of the recursion during parsing
-
-  @param[in] event  an event of type parse_event_t indicating the context in
-  the callback function has been called
-
-  @param[in,out] parsed  the current intermediate parse result; note that
-  writing to this value has no effect for parse_event_t::key events
-
-  @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.
-
-  @sa @ref parse(std::istream&, parser_callback_t) or
-  @ref parse(const CharT, const parser_callback_t) for examples
-
-  @since version 1.0.0
-  */
-  using parser_callback_t =
-      std::function<bool(int depth, parse_event_t event, basic_json &parsed)>;
-
-  //////////////////
-  // constructors //
-  //////////////////
-
-  /// @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 empty value with a given type
-
-  Create an empty JSON value with a given type. The value will be default
-  initialized with an empty value which depends on the type:
-
-  Value type  | initial value
-  ----------- | -------------
-  null        | `null`
-  boolean     | `false`
-  string      | `""`
-  number      | `0`
-  object      | `{}`
-  array       | `[]`
+    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`.
 
-  @param[in] value_type  the type of the value to create
-
-  @complexity Constant.
-
-  @throw std::bad_alloc if allocation for object, array, or string value
-  fails
-
-  @liveexample{The following code shows the constructor for different @ref
-  value_t values,basic_json__value_t}
-
-  @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
-
-  @since version 1.0.0
-  */
-  basic_json(const value_t value_type) : m_type(value_type), m_value(value_type)
-  {
-    assert_invariant();
-  }
-
-  /*!
-  @brief create a null object
-
-  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.
+    #### Storage
 
-  @complexity Constant.
+    Floating-point number values are stored directly inside a @ref basic_json
+    type.
 
-  @exceptionsafety No-throw guarantee: this constructor never throws
-  exceptions.
+    @sa @ref number_integer_t -- type for number values (integer)
 
-  @liveexample{The following code shows the constructor with and without a
-  null pointer parameter.,basic_json__nullptr_t}
+    @sa @ref number_unsigned_t -- type for number values (unsigned integer)
 
-  @since version 1.0.0
-  */
-  basic_json(std::nullptr_t = nullptr) noexcept : basic_json(value_t::null)
-  {
-    assert_invariant();
-  }
+    @since version 1.0.0
+    */
+    using number_float_t = NumberFloatType;
 
-  /*!
-  @brief create an object (explicit)
+    /// @}
 
-  Create an object JSON value with a given content.
+    ///////////////////////////
+    // JSON type enumeration //
+    ///////////////////////////
 
-  @param[in] val  a value for the object
+    /*!
+    @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 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.
+
+    @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.
+
+    @sa @ref 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
+    };
 
-  @complexity Linear in the size of the passed @a val.
+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();
+    }
 
-  @throw std::bad_alloc if allocation for object value fails
+    ////////////////////////
+    // JSON value storage //
+    ////////////////////////
 
-  @liveexample{The following code shows the constructor with an @ref
-  object_t parameter.,basic_json__object_t}
+    /*!
+    @brief a JSON value
+
+    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.
+
+    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*
+
+    @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
+    */
+    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;
+
+        /// 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;
+            }
 
-  @sa @ref basic_json(const CompatibleObjectType&) -- create an object value
-  from a compatible STL container
+            case value_t::array:
+            {
+                array = create<array_t>();
+                break;
+            }
 
-  @since version 1.0.0
-  */
-  basic_json(const object_t &val) : m_type(value_t::object), m_value(val)
-  {
-    assert_invariant();
-  }
+            case value_t::string:
+            {
+                string = create<string_t>("");
+                break;
+            }
 
-  /*!
-  @brief create an object (implicit)
+            case value_t::boolean:
+            {
+                boolean = boolean_t(false);
+                break;
+            }
 
-  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.
-
-  @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.
-
-  @param[in] val  a value for the object
-
-  @complexity Linear in the size of the passed @a val.
-
-  @throw std::bad_alloc if allocation for object value fails
-
-  @liveexample{The following code shows the constructor with several
-  compatible object type parameters.,basic_json__CompatibleObjectType}
-
-  @sa @ref basic_json(const object_t&) -- create an object value
-
-  @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();
-  }
-
-  /*!
-  @brief create an array (explicit)
-
-  Create an array JSON value with a given content.
-
-  @param[in] val  a value for the array
+            case value_t::number_integer:
+            {
+                number_integer = number_integer_t(0);
+                break;
+            }
 
-  @complexity Linear in the size of the passed @a val.
+            case value_t::number_unsigned:
+            {
+                number_unsigned = number_unsigned_t(0);
+                break;
+            }
 
-  @throw std::bad_alloc if allocation for array value fails
+            case value_t::number_float:
+            {
+                number_float = number_float_t(0.0);
+                break;
+            }
 
-  @liveexample{The following code shows the constructor with an @ref array_t
-  parameter.,basic_json__array_t}
+            case value_t::null:
+            {
+                break;
+            }
 
-  @sa @ref basic_json(const CompatibleArrayType&) -- create an array value
-  from a compatible STL containers
+            default:
+            {
+                if (t == value_t::null)
+                {
+                    JSON_THROW(std::domain_error(
+                        "961c151d2e87f2686a955a9be24d316f1362b"
+                        "f21 2.0.10")); // LCOV_EXCL_LINE
+                }
+                break;
+            }
+            }
+        }
 
-  @since version 1.0.0
-  */
-  basic_json(const array_t &val) : m_type(value_t::array), m_value(val)
-  {
-    assert_invariant();
-  }
+        /// constructor for strings
+        json_value(const string_t &value) { string = create<string_t>(value); }
 
-  /*!
-  @brief create an array (implicit)
+        /// constructor for objects
+        json_value(const object_t &value) { object = create<object_t>(value); }
 
-  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.
+        /// constructor for arrays
+        json_value(const array_t &value) { array = create<array_t>(value); }
+    };
 
-  @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.
+    /*!
+    @brief checks the class invariants
 
-  @param[in] val  a value for the array
+    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);
+    }
 
-  @complexity Linear in the size of the passed @a val.
+public:
+    //////////////////////////
+    // JSON parser callback //
+    //////////////////////////
 
-  @throw std::bad_alloc if allocation for array value fails
+    /*!
+    @brief JSON callback events
 
-  @liveexample{The following code shows the constructor with several
-  compatible array type parameters.,basic_json__CompatibleArrayType}
+    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 array_t&) -- create an array value
+    @image html callback_events.png "Example when certain parse events are
+    triggered"
 
-  @since version 1.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();
-  }
+    @since version 1.0.0
+    */
+    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
+    };
 
-  /*!
-  @brief create a string (explicit)
+    /*!
+    @brief per-element parser callback type
+
+    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.
+
+    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.
+
+    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
+
+    @image html callback_events.png "Example when certain parse events are
+    triggered"
+
+    Discarding a value (i.e., returning `false`) has different effects
+    depending on the context in which function was called:
+
+    - 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.
+
+    @param[in] depth  the depth of the recursion during parsing
+
+    @param[in] event  an event of type parse_event_t indicating the context in
+    the callback function has been called
+
+    @param[in,out] parsed  the current intermediate parse result; note that
+    writing to this value has no effect for parse_event_t::key events
+
+    @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.
+
+    @sa @ref parse(std::istream&, parser_callback_t) or
+    @ref parse(const CharT, const parser_callback_t) for examples
+
+    @since version 1.0.0
+    */
+    using parser_callback_t =
+        std::function<bool(int depth, parse_event_t event, basic_json &parsed)>;
 
-  Create an string JSON value with a given content.
-
-  @param[in] val  a value for the string
+    //////////////////
+    // constructors //
+    //////////////////
 
-  @complexity Linear in the size of the passed @a val.
+    /// @name constructors and destructors
+    /// Constructors of class @ref basic_json, copy/move constructor, copy
+    /// assignment, static functions creating objects, and the destructor.
+    /// @{
 
-  @throw std::bad_alloc if allocation for string value fails
+    /*!
+    @brief create an empty value with a given type
 
-  @liveexample{The following code shows the constructor with an @ref
-  string_t parameter.,basic_json__string_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:
 
-  @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
+    Value type  | initial value
+    ----------- | -------------
+    null        | `null`
+    boolean     | `false`
+    string      | `""`
+    number      | `0`
+    object      | `{}`
+    array       | `[]`
 
-  @since version 1.0.0
-  */
-  basic_json(const string_t &val) : m_type(value_t::string), m_value(val)
-  {
-    assert_invariant();
-  }
+    @param[in] value_type  the type of the value to create
 
-  /*!
-  @brief create a string (explicit)
+    @complexity Constant.
 
-  Create a string JSON value with a given content.
+    @throw std::bad_alloc if allocation for object, array, or string value
+    fails
 
-  @param[in] val  a literal value for the string
+    @liveexample{The following code shows the constructor for different @ref
+    value_t values,basic_json__value_t}
 
-  @complexity Linear in the size of the passed @a val.
+    @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
 
-  @throw std::bad_alloc if allocation for string value fails
+    @since version 1.0.0
+    */
+    basic_json(const value_t value_type)
+    : m_type(value_type), m_value(value_type)
+    {
+        assert_invariant();
+    }
 
-  @liveexample{The following code shows the constructor with string literal
-  parameter.,basic_json__string_t_value_type}
+    /*!
+    @brief create a null object
 
-  @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
+    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.
 
-  @since version 1.0.0
-  */
-  basic_json(const typename string_t::value_type *val)
-      : basic_json(string_t(val))
-  {
-    assert_invariant();
-  }
+    @complexity Constant.
 
-  /*!
-  @brief create a string (implicit)
+    @exceptionsafety No-throw guarantee: this constructor never throws
+    exceptions.
 
-  Create a string JSON value with a given content.
+    @liveexample{The following code shows the constructor with and without a
+    null pointer parameter.,basic_json__nullptr_t}
 
-  @param[in] val  a value for the string
+    @since version 1.0.0
+    */
+    basic_json(std::nullptr_t = nullptr) noexcept : basic_json(value_t::null)
+    {
+        assert_invariant();
+    }
 
-  @tparam CompatibleStringType an string type which is compatible to @ref
-  string_t, for instance `std::string`.
+    /*!
+    @brief create an object (explicit)
 
-  @complexity Linear in the size of the passed @a val.
+    Create an object JSON value with a given content.
 
-  @throw std::bad_alloc if allocation for string value fails
-
-  @liveexample{The following code shows the construction of a string value
-  from a compatible type.,basic_json__CompatibleStringType}
-
-  @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
-
-  @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();
-  }
+    @param[in] val  a value for the object
 
-  /*!
-  @brief create a boolean (explicit)
+    @complexity Linear in the size of the passed @a val.
 
-  Creates a JSON boolean type from a given value.
+    @throw std::bad_alloc if allocation for object value fails
 
-  @param[in] val  a boolean value to store
+    @liveexample{The following code shows the constructor with an @ref
+    object_t parameter.,basic_json__object_t}
 
-  @complexity Constant.
+    @sa @ref basic_json(const CompatibleObjectType&) -- create an object value
+    from a compatible STL container
 
-  @liveexample{The example below demonstrates boolean
-  values.,basic_json__boolean_t}
+    @since version 1.0.0
+    */
+    basic_json(const object_t &val) : m_type(value_t::object), m_value(val)
+    {
+        assert_invariant();
+    }
 
-  @since version 1.0.0
-  */
-  basic_json(boolean_t val) noexcept : m_type(value_t::boolean), m_value(val)
-  {
-    assert_invariant();
-  }
+    /*!
+    @brief create an object (implicit)
 
-  /*!
-  @brief create an integer number (explicit)
+    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.
 
-  Create an integer number JSON value with a given content.
+    @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.
 
-  @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.
+    @param[in] val  a value for the object
 
-  @param[in] val  an integer to create a JSON number from
+    @complexity Linear in the size of the passed @a val.
 
-  @complexity Constant.
+    @throw std::bad_alloc if allocation for object value fails
 
-  @liveexample{The example below shows the construction of an integer
-  number value.,basic_json__number_integer_t}
+    @liveexample{The following code shows the constructor with several
+    compatible object type parameters.,basic_json__CompatibleObjectType}
 
-  @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
+    @sa @ref basic_json(const object_t&) -- create an object value
 
-  @since version 1.0.0
-  */
-  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)
-  {
-    assert_invariant();
-  }
+    @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();
+    }
 
-  /*!
-  @brief create an integer number from an enum type (explicit)
+    /*!
+    @brief create an array (explicit)
 
-  Create an integer number JSON value with a given content.
-
-  @param[in] val  an integer to create a JSON number from
+    Create an array JSON value with a given content.
 
-  @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).
+    @param[in] val  a value for the array
 
-  @complexity Constant.
+    @complexity Linear in the size of the passed @a val.
 
-  @liveexample{The example below shows the construction of an integer
-  number value from an anonymous enum.,basic_json__const_int}
+    @throw std::bad_alloc if allocation for array value fails
 
-  @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
-
-  @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))
-  {
-    assert_invariant();
-  }
-
-  /*!
-  @brief create an integer number (implicit)
-
-  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.
-
-  @tparam CompatibleNumberIntegerType An integer type which is compatible to
-  @ref number_integer_t. Examples include the types `int`, `int32_t`,
-  `long`, and `short`.
-
-  @param[in] val  an integer to create a JSON number from
-
-  @complexity Constant.
-
-  @liveexample{The example below shows the construction of several integer
-  number values from compatible
-  types.,basic_json__CompatibleIntegerNumberType}
-
-  @sa @ref basic_json(const number_integer_t) -- create a number value
-  (integer)
-  @sa @ref basic_json(const int) -- create a number value (integer)
-
-  @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();
-  }
-
-  /*!
-  @brief create an unsigned integer number (explicit)
-
-  Create an unsigned integer number JSON value with a given content.
-
-  @tparam T  helper type to compare number_unsigned_t and unsigned int (not
-  visible in) the interface.
-
-  @param[in] val  an integer to create a JSON number from
-
-  @complexity Constant.
-
-  @sa @ref basic_json(const CompatibleNumberUnsignedType) -- create a number
-  value (unsigned integer) from a compatible number type
-
-  @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();
-  }
-
-  /*!
-  @brief create an unsigned number (implicit)
-
-  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.
-
-  @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`.
-
-  @param[in] val  an unsigned integer to create a JSON number from
-
-  @complexity Constant.
-
-  @sa @ref basic_json(const number_unsigned_t) -- create a number value
-  (unsigned)
-
-  @since version 2.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))
-  {
-    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
-
-  @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.
-
-  @complexity Constant.
-
-  @liveexample{The following example creates several floating-point
-  values.,basic_json__number_float_t}
-
-  @sa @ref basic_json(const CompatibleNumberFloatType) -- create a number
-  value (floating-point) from a compatible number type
-
-  @since version 1.0.0
-  */
-  basic_json(const number_float_t val) noexcept : m_type(value_t::number_float),
-                                                  m_value(val)
-  {
-    // 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)
-
-  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.
-
-  @tparam CompatibleNumberFloatType A floating-point type which is
-  compatible to @ref number_float_t. Examples may include the types `float`
-  or `double`.
-
-  @param[in] val  a floating-point to create a JSON number from
-
-  @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.
-
-  @complexity Constant.
-
-  @liveexample{The example below shows the construction of several
-  floating-point number values from compatible
-  types.,basic_json__CompatibleNumberFloatType}
-
-  @sa @ref basic_json(const number_float_t) -- create a number value
-  (floating-point)
-
-  @since version 1.0.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))
-  {
-    assert_invariant();
-  }
-
-  /*!
-  @brief create a container (array or object) from an initializer list
-
-  Creates a JSON value of type array or object from the passed initializer
-  list @a init. In case @a type_deduction is `true` (default), the type of
-  the JSON value to be created is deducted from the initializer list @a init
-  according to the following rules:
-
-  1. If the list is empty, an empty JSON object value `{}` is created.
-  2. If the list consists of pairs whose first element is a string, a JSON
-     object value is created where the first elements of the pairs are
-     treated as keys and the second elements are as values.
-  3. In all other cases, an array is created.
-
-  The rules aim to create the best fit between a C++ initializer list and
-  JSON values. The rationale is as follows:
-
-  1. The empty initializer list is written as `{}` which is exactly an empty
-     JSON object.
-  2. C++ has now way of describing mapped types other than to list a list of
-     pairs. As JSON requires that keys must be of type string, rule 2 is the
-     weakest constraint one can pose on initializer lists to interpret them
-     as an object.
-  3. In all other cases, the initializer list could not be interpreted as
-     JSON object type, so interpreting it as JSON array type is safe.
-
-  With the rules described above, the following JSON values cannot be
-  expressed by an initializer list:
-
-  - the empty array (`[]`): use @ref array(std::initializer_list<basic_json>)
-    with an empty initializer list in this case
-  - arrays whose elements satisfy rule 2: use @ref
-    array(std::initializer_list<basic_json>) with the same initializer list
-    in this case
-
-  @note When used without parentheses around an empty initializer list, @ref
-  basic_json() is called instead of this function, yielding the JSON null
-  value.
-
-  @param[in] init  initializer list with JSON values
-
-  @param[in] type_deduction internal parameter; when set to `true`, the type
-  of the JSON value is deducted from the initializer list @a init; when set
-  to `false`, the type provided via @a manual_type is forced. This mode is
-  used by the functions @ref array(std::initializer_list<basic_json>) and
-  @ref object(std::initializer_list<basic_json>).
-
-  @param[in] manual_type internal parameter; when @a type_deduction is set
-  to `false`, the created JSON value will use the provided type (only @ref
-  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"`
-
-  @complexity Linear in the size of the initializer list @a init.
-
-  @liveexample{The example below shows how JSON values are created from
-  initializer lists.,basic_json__list_init_t}
-
-  @sa @ref array(std::initializer_list<basic_json>) -- create a JSON array
-  value from an initializer list
-  @sa @ref object(std::initializer_list<basic_json>) -- create a JSON object
-  value from an initializer list
-
-  @since version 1.0.0
-  */
-  basic_json(std::initializer_list<basic_json> init, bool type_deduction = true,
-             value_t manual_type = value_t::array)
-  {
-    // check if each element is an array with two elements whose first
-    // element is a string
-    bool is_an_object =
-        std::all_of(init.begin(), init.end(), [](const basic_json &element) {
-          return element.is_array() and element.size() == 2 and
-                 element[0].is_string();
-        });
-
-    // adjust type if type deduction is not wanted
-    if (not type_deduction)
-    {
-      // if array is wanted, do not create an object though possible
-      if (manual_type == value_t::array)
-      {
-        is_an_object = false;
-      }
-
-      // 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"));
-      }
-    }
+    @liveexample{The following code shows the constructor with an @ref array_t
+    parameter.,basic_json__array_t}
 
-    if (is_an_object)
-    {
-      // the initializer list is a list of pairs -> create object
-      m_type = value_t::object;
-      m_value = value_t::object;
+    @sa @ref basic_json(const CompatibleArrayType&) -- create an array value
+    from a compatible STL containers
 
-      std::for_each(
-          init.begin(), init.end(), [this](const basic_json &element) {
-            m_value.object->emplace(*(element[0].m_value.string), element[1]);
-          });
-    }
-    else
+    @since version 1.0.0
+    */
+    basic_json(const array_t &val) : m_type(value_t::array), m_value(val)
     {
-      // the initializer list describes an array -> create array
-      m_type = value_t::array;
-      m_value.array = create<array_t>(init);
+        assert_invariant();
     }
 
-    assert_invariant();
-  }
+    /*!
+    @brief create an array (implicit)
+
+    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.
 
-  /*!
-  @brief explicitly create an array from an initializer list
+    @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.
 
-  Creates a JSON array value from a given initializer list. That is, given a
-  list of values `a, b, c`, creates the JSON value `[a, b, c]`. If the
-  initializer list is empty, the empty array `[]` is created.
+    @param[in] val  a value for the array
 
-  @note This function is only needed to express two edge cases that cannot
-  be realized with the initializer list constructor (@ref
-  basic_json(std::initializer_list<basic_json>, bool, value_t)). These cases
-  are:
-  1. creating an array whose elements are all pairs whose first element is a
-  string -- in this case, the initializer list constructor would create an
-  object, taking the first elements as keys
-  2. creating an empty array -- passing the empty initializer list to the
-  initializer list constructor yields an empty object
+    @complexity Linear in the size of the passed @a val.
 
-  @param[in] init  initializer list with JSON values to create an array from
-  (optional)
+    @throw std::bad_alloc if allocation for array value fails
 
-  @return JSON array value
+    @liveexample{The following code shows the constructor with several
+    compatible array type parameters.,basic_json__CompatibleArrayType}
 
-  @complexity Linear in the size of @a init.
+    @sa @ref basic_json(const array_t&) -- create an array value
 
-  @liveexample{The following code shows an example for the `array`
-  function.,array}
+    @since version 1.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();
+    }
 
-  @sa @ref basic_json(std::initializer_list<basic_json>, bool, value_t) --
-  create a JSON value from an initializer list
-  @sa @ref object(std::initializer_list<basic_json>) -- create a JSON object
-  value from an initializer list
+    /*!
+    @brief create a string (explicit)
 
-  @since version 1.0.0
-  */
-  static basic_json array(std::initializer_list<basic_json> init =
-                              std::initializer_list<basic_json>())
-  {
-    return basic_json(init, false, value_t::array);
-  }
+    Create an string JSON value with a given content.
 
-  /*!
-  @brief explicitly create an object from an initializer list
+    @param[in] val  a value for the string
 
-  Creates a JSON object value from a given initializer list. The initializer
-  lists elements must be pairs, and their first elements must be strings. If
-  the initializer list is empty, the empty object `{}` is created.
-
-  @note This function is only added for symmetry reasons. In contrast to the
-  related function @ref array(std::initializer_list<basic_json>), there are
-  no cases which can only be expressed by this function. That is, any
-  initializer list @a init can also be passed to the initializer list
-  constructor @ref basic_json(std::initializer_list<basic_json>, bool,
-  value_t).
-
-  @param[in] init  initializer list to create an object from (optional)
-
-  @return JSON object value
+    @complexity Linear in the size of the passed @a val.
 
-  @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 std::bad_alloc if allocation for string value fails
 
-  @complexity Linear in the size of @a init.
+    @liveexample{The following code shows the constructor with an @ref
+    string_t parameter.,basic_json__string_t}
 
-  @liveexample{The following code shows an example for the `object`
-  function.,object}
+    @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
 
-  @sa @ref basic_json(std::initializer_list<basic_json>, bool, value_t) --
-  create a JSON value from an initializer list
-  @sa @ref array(std::initializer_list<basic_json>) -- create a JSON array
-  value from an initializer list
-
-  @since version 1.0.0
-  */
-  static basic_json object(std::initializer_list<basic_json> init =
-                               std::initializer_list<basic_json>())
-  {
-    return basic_json(init, false, value_t::object);
-  }
-
-  /*!
-  @brief construct an array with count copies of given value
-
-  Constructs a JSON array value by creating @a cnt copies of a passed value.
-  In case @a cnt is `0`, an empty array is created. As postcondition,
-  `std::distance(begin(),end()) == cnt` holds.
+    @since version 1.0.0
+    */
+    basic_json(const string_t &val) : m_type(value_t::string), m_value(val)
+    {
+        assert_invariant();
+    }
 
-  @param[in] cnt  the number of JSON copies of @a val to create
-  @param[in] val  the JSON value to copy
+    /*!
+    @brief create a string (explicit)
 
-  @complexity Linear in @a cnt.
+    Create a string JSON value with a given content.
 
-  @liveexample{The following code shows examples for the @ref
-  basic_json(size_type\, const basic_json&)
-  constructor.,basic_json__size_type_basic_json}
-
-  @since version 1.0.0
-  */
-  basic_json(size_type cnt, const basic_json &val) : m_type(value_t::array)
-  {
-    m_value.array = create<array_t>(cnt, val);
-    assert_invariant();
-  }
-
-  /*!
-  @brief construct a JSON container given an iterator range
-
-  Constructs the JSON value with the contents of the range `[first, last)`.
-  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.
-  - 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.
-
-  @tparam InputIT an input iterator type (@ref iterator or @ref
-  const_iterator)
-
-  @param[in] first begin of the range to copy from (included)
-  @param[in] last end of the range to copy from (excluded)
-
-  @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"`
-
-  @complexity Linear in distance between @a first and @a last.
-
-  @liveexample{The example below shows several ways to create JSON values by
-  specifying a subrange with iterators.,basic_json__InputIt_InputIt}
-
-  @since version 1.0.0
-  */
-  template <class InputIT,
-            typename std::enable_if<
-                std::is_same<InputIT, typename basic_json_t::iterator>::value or
-                    std::is_same<InputIT,
-                                 typename basic_json_t::const_iterator>::value,
-                int>::type = 0>
-  basic_json(InputIT first, InputIT last)
-  {
-    assert(first.m_object != nullptr);
-    assert(last.m_object != nullptr);
-
-    // make sure iterator fits the current value
-    if (first.m_object != last.m_object)
-    {
-      JSON_THROW(std::domain_error("iterators are not compatible"));
-    }
-
-    // copy type from first iterator
-    m_type = first.m_object->m_type;
-
-    // check if iterator range is complete for primitive values
-    switch (m_type)
-    {
-      case value_t::boolean:
-      case value_t::number_float:
-      case value_t::number_integer:
-      case value_t::number_unsigned:
-      case value_t::string:
-      {
-        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"));
-        }
-        break;
-      }
-
-      default:
-      {
-        break;
-      }
-    }
-
-    switch (m_type)
-    {
-      case value_t::number_integer:
-      {
-        m_value.number_integer = first.m_object->m_value.number_integer;
-        break;
-      }
-
-      case value_t::number_unsigned:
-      {
-        m_value.number_unsigned = first.m_object->m_value.number_unsigned;
-        break;
-      }
-
-      case value_t::number_float:
-      {
-        m_value.number_float = first.m_object->m_value.number_float;
-        break;
-      }
-
-      case value_t::boolean:
-      {
-        m_value.boolean = first.m_object->m_value.boolean;
-        break;
-      }
-
-      case value_t::string:
-      {
-        m_value = *first.m_object->m_value.string;
-        break;
-      }
-
-      case value_t::object:
-      {
-        m_value.object = create<object_t>(first.m_it.object_iterator,
-                                          last.m_it.object_iterator);
-        break;
-      }
-
-      case value_t::array:
-      {
-        m_value.array = create<array_t>(first.m_it.array_iterator,
-                                        last.m_it.array_iterator);
-        break;
-      }
-
-      default:
-      {
-        JSON_THROW(
-            std::domain_error("cannot use 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();
-  }
+    @param[in] val  a literal value for the string
 
-  ///////////////////////////////////////
-  // other constructors and destructor //
-  ///////////////////////////////////////
+    @complexity Linear in the size of the passed @a val.
 
-  /*!
-  @brief copy constructor
-
-  Creates a copy of a given JSON value.
-
-  @param[in] other  the JSON value to copy
-
-  @complexity Linear in the size of @a other.
-
-  @requirement This function helps `basic_json` satisfying the
-  [Container](http://en.cppreference.com/w/cpp/concept/Container)
-  requirements:
-  - The complexity is linear.
-  - As postcondition, it holds: `other == basic_json(other)`.
+    @throw std::bad_alloc if allocation for string value fails
 
-  @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}
+    @liveexample{The following code shows the constructor with string literal
+    parameter.,basic_json__string_t_value_type}
 
-  @since version 1.0.0
-  */
-  basic_json(const basic_json &other) : m_type(other.m_type)
-  {
-    // check of passed value is valid
-    other.assert_invariant();
+    @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
 
-    switch (m_type)
+    @since version 1.0.0
+    */
+    basic_json(const typename string_t::value_type *val)
+    : basic_json(string_t(val))
     {
-      case value_t::object:
-      {
-        m_value = *other.m_value.object;
-        break;
-      }
-
-      case value_t::array:
-      {
-        m_value = *other.m_value.array;
-        break;
-      }
-
-      case value_t::string:
-      {
-        m_value = *other.m_value.string;
-        break;
-      }
-
-      case value_t::boolean:
-      {
-        m_value = other.m_value.boolean;
-        break;
-      }
-
-      case value_t::number_integer:
-      {
-        m_value = other.m_value.number_integer;
-        break;
-      }
-
-      case value_t::number_unsigned:
-      {
-        m_value = other.m_value.number_unsigned;
-        break;
-      }
-
-      case value_t::number_float:
-      {
-        m_value = other.m_value.number_float;
-        break;
-      }
-
-      default:
-      {
-        break;
-      }
-    }
-
-    assert_invariant();
-  }
-
-  /*!
-  @brief move constructor
-
-  Move constructor. Constructs a JSON value with the contents of the given
-  value @a other using move semantics. It "steals" the resources from @a
-  other and leaves it as JSON null value.
-
-  @param[in,out] other  value to move to this object
-
-  @post @a other is a JSON null value
-
-  @complexity Constant.
-
-  @liveexample{The code below shows the move constructor explicitly called
-  via std::move.,basic_json__moveconstructor}
-
-  @since version 1.0.0
-  */
-  basic_json(basic_json &&other) noexcept : m_type(std::move(other.m_type)),
-                                            m_value(std::move(other.m_value))
-  {
-    // check that passed value is valid
-    other.assert_invariant();
-
-    // invalidate payload
-    other.m_type = value_t::null;
-    other.m_value = {};
-
-    assert_invariant();
-  }
-
-  /*!
-  @brief copy assignment
-
-  Copy assignment operator. Copies a JSON value via the "copy and swap"
-  strategy: It is expressed in terms of the copy constructor, destructor,
-  and the swap() member function.
-
-  @param[in] other  value to copy from
-
-  @complexity Linear.
-
-  @requirement This function helps `basic_json` satisfying the
-  [Container](http://en.cppreference.com/w/cpp/concept/Container)
-  requirements:
-  - The complexity is linear.
-
-  @liveexample{The code below shows and example for the copy assignment. It
-  creates a copy of value `a` which is then swapped with `b`. Finally\, the
-  copy of `a` (which is the null value after the swap) is
-  destroyed.,basic_json__copyassignment}
-
-  @since version 1.0.0
-  */
-  reference &operator=(basic_json other) noexcept(
-      std::is_nothrow_move_constructible<value_t>::value
-          and std::is_nothrow_move_assignable<value_t>::value
-              and std::is_nothrow_move_constructible<json_value>::value
-                  and std::is_nothrow_move_assignable<json_value>::value)
-  {
-    // check that passed value is valid
-    other.assert_invariant();
-
-    using std::swap;
-    swap(m_type, other.m_type);
-    swap(m_value, other.m_value);
-
-    assert_invariant();
-    return *this;
-  }
-
-  /*!
-  @brief destructor
-
-  Destroys the JSON value and frees all allocated memory.
-
-  @complexity Linear.
-
-  @requirement This function helps `basic_json` satisfying the
-  [Container](http://en.cppreference.com/w/cpp/concept/Container)
-  requirements:
-  - The complexity is linear.
-  - All stored elements are destroyed and all memory is freed.
+        assert_invariant();
+    }
 
-  @since version 1.0.0
-  */
-  ~basic_json()
-  {
-    assert_invariant();
+    /*!
+    @brief create a string (implicit)
 
-    switch (m_type)
-    {
-      case value_t::object:
-      {
-        AllocatorType<object_t> alloc;
-        alloc.destroy(m_value.object);
-        alloc.deallocate(m_value.object, 1);
-        break;
-      }
+    Create a string JSON value with a given content.
 
-      case value_t::array:
-      {
-        AllocatorType<array_t> alloc;
-        alloc.destroy(m_value.array);
-        alloc.deallocate(m_value.array, 1);
-        break;
-      }
-
-      case value_t::string:
-      {
-        AllocatorType<string_t> alloc;
-        alloc.destroy(m_value.string);
-        alloc.deallocate(m_value.string, 1);
-        break;
-      }
-
-      default:
-      {
-        // all other types need no specific destructor
-        break;
-      }
-    }
-  }
-
-  /// @}
+    @param[in] val  a value for the string
 
-public:
-  ///////////////////////
-  // object inspection //
-  ///////////////////////
+    @tparam CompatibleStringType an string type which is compatible to @ref
+    string_t, for instance `std::string`.
 
-  /// @name object inspection
-  /// Functions to inspect the type of a JSON value.
-  /// @{
+    @complexity Linear in the size of the passed @a val.
 
-  /*!
-  @brief serialization
+    @throw std::bad_alloc if allocation for string value fails
 
-  Serialization function for JSON values. The function tries to mimic
-  Python's `json.dumps()` function, and currently supports its @a indent
-  parameter.
+    @liveexample{The following code shows the construction of a string value
+    from a compatible type.,basic_json__CompatibleStringType}
 
-  @param[in] indent If indent is nonnegative, then array elements and object
-  members will be pretty-printed with that indent level. An indent level of
-  `0` will only insert newlines. `-1` (the default) selects the most compact
-  representation.
+    @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
 
-  @return string containing the serialization of the JSON value
+    @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();
+    }
 
-  @complexity Linear.
+    /*!
+    @brief create a boolean (explicit)
 
-  @liveexample{The following example shows the effect of different @a indent
-  parameters to the result of the serialization.,dump}
+    Creates a JSON boolean type from a given value.
 
-  @see https://docs.python.org/2/library/json.html#json.dump
+    @param[in] val  a boolean value to store
 
-  @since version 1.0.0
-  */
-  string_t dump(const int indent = -1) const
-  {
-    std::stringstream ss;
-    // fix locale problems
-    ss.imbue(std::locale::classic());
+    @complexity Constant.
 
-    // 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);
+    @liveexample{The example below demonstrates boolean
+    values.,basic_json__boolean_t}
 
-    if (indent >= 0)
-    {
-      dump(ss, true, static_cast<unsigned int>(indent));
-    }
-    else
+    @since version 1.0.0
+    */
+    basic_json(boolean_t val) noexcept : m_type(value_t::boolean), m_value(val)
     {
-      dump(ss, false, 0);
+        assert_invariant();
     }
 
-    return ss.str();
-  }
+    /*!
+    @brief create an integer number (explicit)
 
-  /*!
-  @brief return the type of the JSON value (explicit)
+    Create an integer number JSON value with a given content.
 
-  Return the type of the JSON value as a value from the @ref value_t
-  enumeration.
+    @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.
 
-  @return the type of the JSON value
+    @param[in] val  an integer to create a JSON number from
 
-  @complexity Constant.
+    @complexity Constant.
 
-  @exceptionsafety No-throw guarantee: this member function never throws
-  exceptions.
+    @liveexample{The example below shows the construction of an integer
+    number value.,basic_json__number_integer_t}
 
-  @liveexample{The following code exemplifies `type()` for all JSON
-  types.,type}
+    @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
 
-  @since version 1.0.0
-  */
-  constexpr value_t type() const noexcept { return m_type; }
+    @since version 1.0.0
+    */
+    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)
+    {
+        assert_invariant();
+    }
 
-  /*!
-  @brief return whether type is primitive
+    /*!
+    @brief create an integer number from an enum type (explicit)
 
-  This function returns true iff the JSON type is primitive (string, number,
-  boolean, or null).
+    Create an integer number JSON value with a given content.
 
-  @return `true` if type is primitive (string, number, boolean, or null),
-  `false` otherwise.
+    @param[in] val  an integer to create a JSON number from
 
-  @complexity Constant.
+    @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).
 
-  @exceptionsafety No-throw guarantee: this member function never throws
-  exceptions.
+    @complexity Constant.
 
-  @liveexample{The following code exemplifies `is_primitive()` for all JSON
-  types.,is_primitive}
+    @liveexample{The example below shows the construction of an integer
+    number value from an anonymous enum.,basic_json__const_int}
 
-  @sa @ref is_structured() -- returns whether JSON value is structured
-  @sa @ref is_null() -- returns whether JSON value is `null`
-  @sa @ref is_string() -- returns whether JSON value is a string
-  @sa @ref is_boolean() -- returns whether JSON value is a boolean
-  @sa @ref is_number() -- returns whether JSON value is a number
+    @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
 
-  @since version 1.0.0
-  */
-  constexpr bool is_primitive() const noexcept
-  {
-    return is_null() or is_string() or is_boolean() or is_number();
-  }
+    @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))
+    {
+        assert_invariant();
+    }
 
-  /*!
-  @brief return whether type is structured
+    /*!
+    @brief create an integer number (implicit)
 
-  This function returns true iff the JSON type is structured (array or
-  object).
+    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.
 
-  @return `true` if type is structured (array or object), `false` otherwise.
+    @tparam CompatibleNumberIntegerType An integer type which is compatible to
+    @ref number_integer_t. Examples include the types `int`, `int32_t`,
+    `long`, and `short`.
 
-  @complexity Constant.
+    @param[in] val  an integer to create a JSON number from
 
-  @exceptionsafety No-throw guarantee: this member function never throws
-  exceptions.
+    @complexity Constant.
 
-  @liveexample{The following code exemplifies `is_structured()` for all JSON
-  types.,is_structured}
+    @liveexample{The example below shows the construction of several integer
+    number values from compatible
+    types.,basic_json__CompatibleIntegerNumberType}
 
-  @sa @ref is_primitive() -- returns whether value is primitive
-  @sa @ref is_array() -- returns whether value is an array
-  @sa @ref is_object() -- returns whether value is an object
+    @sa @ref basic_json(const number_integer_t) -- create a number value
+    (integer)
+    @sa @ref basic_json(const int) -- create a number value (integer)
 
-  @since version 1.0.0
-  */
-  constexpr bool is_structured() const noexcept
-  {
-    return is_array() or is_object();
-  }
+    @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();
+    }
 
-  /*!
-  @brief return whether value is null
+    /*!
+    @brief create an unsigned integer number (explicit)
 
-  This function returns true iff the JSON value is null.
+    Create an unsigned integer number JSON value with a given content.
 
-  @return `true` if type is null, `false` otherwise.
+    @tparam T  helper type to compare number_unsigned_t and unsigned int (not
+    visible in) the interface.
 
-  @complexity Constant.
+    @param[in] val  an integer to create a JSON number from
 
-  @exceptionsafety No-throw guarantee: this member function never throws
-  exceptions.
+    @complexity Constant.
 
-  @liveexample{The following code exemplifies `is_null()` for all JSON
-  types.,is_null}
+    @sa @ref basic_json(const CompatibleNumberUnsignedType) -- create a number
+    value (unsigned integer) from a compatible number type
 
-  @since version 1.0.0
-  */
-  constexpr bool is_null() const noexcept { return m_type == value_t::null; }
+    @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();
+    }
 
-  /*!
-  @brief return whether value is a boolean
+    /*!
+    @brief create an unsigned number (implicit)
 
-  This function returns true iff the JSON value is a boolean.
+    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.
 
-  @return `true` if type is boolean, `false` otherwise.
+    @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`.
 
-  @complexity Constant.
+    @param[in] val  an unsigned integer to create a JSON number from
 
-  @exceptionsafety No-throw guarantee: this member function never throws
-  exceptions.
+    @complexity Constant.
 
-  @liveexample{The following code exemplifies `is_boolean()` for all JSON
-  types.,is_boolean}
+    @sa @ref basic_json(const number_unsigned_t) -- create a number value
+    (unsigned)
 
-  @since version 1.0.0
-  */
-  constexpr bool is_boolean() const noexcept
-  {
-    return m_type == value_t::boolean;
-  }
+    @since version 2.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))
+    {
+        assert_invariant();
+    }
 
-  /*!
-  @brief return whether value is a number
+    /*!
+    @brief create a floating-point number (explicit)
 
-  This function returns true iff the JSON value is a number. This includes
-  both integer and floating-point values.
+    Create a floating-point number JSON value with a given content.
 
-  @return `true` if type is number (regardless whether integer, unsigned
-  integer or floating-type), `false` otherwise.
+    @param[in] val  a floating-point value to create a JSON number from
 
-  @complexity Constant.
+    @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.
 
-  @exceptionsafety No-throw guarantee: this member function never throws
-  exceptions.
+    @complexity Constant.
 
-  @liveexample{The following code exemplifies `is_number()` for all JSON
-  types.,is_number}
+    @liveexample{The following example creates several floating-point
+    values.,basic_json__number_float_t}
 
-  @sa @ref is_number_integer() -- check if value is an integer or unsigned
-  integer number
-  @sa @ref is_number_unsigned() -- check if value is an unsigned integer
-  number
-  @sa @ref is_number_float() -- check if value is a floating-point number
+    @sa @ref basic_json(const CompatibleNumberFloatType) -- create a number
+    value (floating-point) from a compatible number type
 
-  @since version 1.0.0
-  */
-  constexpr bool is_number() const noexcept
-  {
-    return is_number_integer() or is_number_float();
-  }
+    @since version 1.0.0
+    */
+    basic_json(const number_float_t val) noexcept
+        : m_type(value_t::number_float),
+          m_value(val)
+    {
+        // replace infinity and NAN by null
+        if (not std::isfinite(val))
+        {
+            m_type = value_t::null;
+            m_value = json_value();
+        }
 
-  /*!
-  @brief return whether value is an integer number
+        assert_invariant();
+    }
 
-  This function returns true iff the JSON value is an integer or unsigned
-  integer number. This excludes floating-point values.
+    /*!
+    @brief create an floating-point number (implicit)
 
-  @return `true` if type is an integer or unsigned integer number, `false`
-  otherwise.
+    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.
 
-  @complexity Constant.
+    @tparam CompatibleNumberFloatType A floating-point type which is
+    compatible to @ref number_float_t. Examples may include the types `float`
+    or `double`.
 
-  @exceptionsafety No-throw guarantee: this member function never throws
-  exceptions.
+    @param[in] val  a floating-point to create a JSON number from
 
-  @liveexample{The following code exemplifies `is_number_integer()` for all
-  JSON types.,is_number_integer}
+    @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.
 
-  @sa @ref is_number() -- check if value is a number
-  @sa @ref is_number_unsigned() -- check if value is an unsigned integer
-  number
-  @sa @ref is_number_float() -- check if value is a floating-point number
+    @complexity Constant.
 
-  @since version 1.0.0
-  */
-  constexpr bool is_number_integer() const noexcept
-  {
-    return m_type == value_t::number_integer or
-           m_type == value_t::number_unsigned;
-  }
+    @liveexample{The example below shows the construction of several
+    floating-point number values from compatible
+    types.,basic_json__CompatibleNumberFloatType}
 
-  /*!
-  @brief return whether value is an unsigned integer number
+    @sa @ref basic_json(const number_float_t) -- create a number value
+    (floating-point)
 
-  This function returns true iff the JSON value is an unsigned integer
-  number. This excludes floating-point and (signed) integer values.
+    @since version 1.0.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))
+    {
+        assert_invariant();
+    }
 
-  @return `true` if type is an unsigned integer number, `false` otherwise.
+    /*!
+    @brief create a container (array or object) from an initializer list
+
+    Creates a JSON value of type array or object from the passed initializer
+    list @a init. In case @a type_deduction is `true` (default), the type of
+    the JSON value to be created is deducted from the initializer list @a init
+    according to the following rules:
+
+    1. If the list is empty, an empty JSON object value `{}` is created.
+    2. If the list consists of pairs whose first element is a string, a JSON
+       object value is created where the first elements of the pairs are
+       treated as keys and the second elements are as values.
+    3. In all other cases, an array is created.
+
+    The rules aim to create the best fit between a C++ initializer list and
+    JSON values. The rationale is as follows:
+
+    1. The empty initializer list is written as `{}` which is exactly an empty
+       JSON object.
+    2. C++ has now way of describing mapped types other than to list a list of
+       pairs. As JSON requires that keys must be of type string, rule 2 is the
+       weakest constraint one can pose on initializer lists to interpret them
+       as an object.
+    3. In all other cases, the initializer list could not be interpreted as
+       JSON object type, so interpreting it as JSON array type is safe.
+
+    With the rules described above, the following JSON values cannot be
+    expressed by an initializer list:
+
+    - the empty array (`[]`): use @ref array(std::initializer_list<basic_json>)
+      with an empty initializer list in this case
+    - arrays whose elements satisfy rule 2: use @ref
+      array(std::initializer_list<basic_json>) with the same initializer list
+      in this case
+
+    @note When used without parentheses around an empty initializer list, @ref
+    basic_json() is called instead of this function, yielding the JSON null
+    value.
+
+    @param[in] init  initializer list with JSON values
+
+    @param[in] type_deduction internal parameter; when set to `true`, the type
+    of the JSON value is deducted from the initializer list @a init; when set
+    to `false`, the type provided via @a manual_type is forced. This mode is
+    used by the functions @ref array(std::initializer_list<basic_json>) and
+    @ref object(std::initializer_list<basic_json>).
+
+    @param[in] manual_type internal parameter; when @a type_deduction is set
+    to `false`, the created JSON value will use the provided type (only @ref
+    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"`
+
+    @complexity Linear in the size of the initializer list @a init.
+
+    @liveexample{The example below shows how JSON values are created from
+    initializer lists.,basic_json__list_init_t}
+
+    @sa @ref array(std::initializer_list<basic_json>) -- create a JSON array
+    value from an initializer list
+    @sa @ref object(std::initializer_list<basic_json>) -- create a JSON object
+    value from an initializer list
+
+    @since version 1.0.0
+    */
+    basic_json(std::initializer_list<basic_json> init,
+               bool type_deduction = true, value_t manual_type = value_t::array)
+    {
+        // check if each element is an array with two elements whose first
+        // element is a string
+        bool is_an_object = std::all_of(
+            init.begin(), init.end(), [](const basic_json &element) {
+                return element.is_array() and element.size() == 2 and
+                       element[0].is_string();
+            });
+
+        // adjust type if type deduction is not wanted
+        if (not type_deduction)
+        {
+            // if array is wanted, do not create an object though possible
+            if (manual_type == value_t::array)
+            {
+                is_an_object = false;
+            }
 
-  @complexity Constant.
+            // 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"));
+            }
+        }
 
-  @exceptionsafety No-throw guarantee: this member function never throws
-  exceptions.
+        if (is_an_object)
+        {
+            // the initializer list is a list of pairs -> create object
+            m_type = value_t::object;
+            m_value = value_t::object;
 
-  @liveexample{The following code exemplifies `is_number_unsigned()` for all
-  JSON types.,is_number_unsigned}
+            std::for_each(init.begin(), init.end(),
+                          [this](const basic_json &element) {
+                              m_value.object->emplace(
+                                  *(element[0].m_value.string), element[1]);
+                          });
+        }
+        else
+        {
+            // the initializer list describes an array -> create array
+            m_type = value_t::array;
+            m_value.array = create<array_t>(init);
+        }
 
-  @sa @ref is_number() -- check if value is a number
-  @sa @ref is_number_integer() -- check if value is an integer or unsigned
-  integer number
-  @sa @ref is_number_float() -- check if value is a floating-point number
+        assert_invariant();
+    }
 
-  @since version 2.0.0
-  */
-  constexpr bool is_number_unsigned() const noexcept
-  {
-    return m_type == value_t::number_unsigned;
-  }
+    /*!
+    @brief explicitly create an array from an initializer list
 
-  /*!
-  @brief return whether value is a floating-point number
+    Creates a JSON array value from a given initializer list. That is, given a
+    list of values `a, b, c`, creates the JSON value `[a, b, c]`. If the
+    initializer list is empty, the empty array `[]` is created.
 
-  This function returns true iff the JSON value is a floating-point number.
-  This excludes integer and unsigned integer values.
+    @note This function is only needed to express two edge cases that cannot
+    be realized with the initializer list constructor (@ref
+    basic_json(std::initializer_list<basic_json>, bool, value_t)). These cases
+    are:
+    1. creating an array whose elements are all pairs whose first element is a
+    string -- in this case, the initializer list constructor would create an
+    object, taking the first elements as keys
+    2. creating an empty array -- passing the empty initializer list to the
+    initializer list constructor yields an empty object
 
-  @return `true` if type is a floating-point number, `false` otherwise.
+    @param[in] init  initializer list with JSON values to create an array from
+    (optional)
 
-  @complexity Constant.
+    @return JSON array value
 
-  @exceptionsafety No-throw guarantee: this member function never throws
-  exceptions.
+    @complexity Linear in the size of @a init.
 
-  @liveexample{The following code exemplifies `is_number_float()` for all
-  JSON types.,is_number_float}
+    @liveexample{The following code shows an example for the `array`
+    function.,array}
 
-  @sa @ref is_number() -- check if value is number
-  @sa @ref is_number_integer() -- check if value is an integer number
-  @sa @ref is_number_unsigned() -- check if value is an unsigned integer
-  number
+    @sa @ref basic_json(std::initializer_list<basic_json>, bool, value_t) --
+    create a JSON value from an initializer list
+    @sa @ref object(std::initializer_list<basic_json>) -- create a JSON object
+    value from an initializer list
 
-  @since version 1.0.0
-  */
-  constexpr bool is_number_float() const noexcept
-  {
-    return m_type == value_t::number_float;
-  }
+    @since version 1.0.0
+    */
+    static basic_json array(std::initializer_list<basic_json> init =
+                                std::initializer_list<basic_json>())
+    {
+        return basic_json(init, false, value_t::array);
+    }
 
-  /*!
-  @brief return whether value is an object
+    /*!
+    @brief explicitly create an object from an initializer list
 
-  This function returns true iff the JSON value is an object.
+    Creates a JSON object value from a given initializer list. The initializer
+    lists elements must be pairs, and their first elements must be strings. If
+    the initializer list is empty, the empty object `{}` is created.
 
-  @return `true` if type is object, `false` otherwise.
+    @note This function is only added for symmetry reasons. In contrast to the
+    related function @ref array(std::initializer_list<basic_json>), there are
+    no cases which can only be expressed by this function. That is, any
+    initializer list @a init can also be passed to the initializer list
+    constructor @ref basic_json(std::initializer_list<basic_json>, bool,
+    value_t).
 
-  @complexity Constant.
+    @param[in] init  initializer list to create an object from (optional)
 
-  @exceptionsafety No-throw guarantee: this member function never throws
-  exceptions.
+    @return JSON object value
 
-  @liveexample{The following code exemplifies `is_object()` for all JSON
-  types.,is_object}
+    @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)
 
-  @since version 1.0.0
-  */
-  constexpr bool is_object() const noexcept
-  {
-    return m_type == value_t::object;
-  }
+    @complexity Linear in the size of @a init.
 
-  /*!
-  @brief return whether value is an array
+    @liveexample{The following code shows an example for the `object`
+    function.,object}
 
-  This function returns true iff the JSON value is an array.
+    @sa @ref basic_json(std::initializer_list<basic_json>, bool, value_t) --
+    create a JSON value from an initializer list
+    @sa @ref array(std::initializer_list<basic_json>) -- create a JSON array
+    value from an initializer list
 
-  @return `true` if type is array, `false` otherwise.
+    @since version 1.0.0
+    */
+    static basic_json object(std::initializer_list<basic_json> init =
+                                 std::initializer_list<basic_json>())
+    {
+        return basic_json(init, false, value_t::object);
+    }
 
-  @complexity Constant.
+    /*!
+    @brief construct an array with count copies of given value
 
-  @exceptionsafety No-throw guarantee: this member function never throws
-  exceptions.
+    Constructs a JSON array value by creating @a cnt copies of a passed value.
+    In case @a cnt is `0`, an empty array is created. As postcondition,
+    `std::distance(begin(),end()) == cnt` holds.
 
-  @liveexample{The following code exemplifies `is_array()` for all JSON
-  types.,is_array}
+    @param[in] cnt  the number of JSON copies of @a val to create
+    @param[in] val  the JSON value to copy
 
-  @since version 1.0.0
-  */
-  constexpr bool is_array() const noexcept { return m_type == value_t::array; }
+    @complexity Linear in @a cnt.
 
-  /*!
-  @brief return whether value is a string
+    @liveexample{The following code shows examples for the @ref
+    basic_json(size_type\, const basic_json&)
+    constructor.,basic_json__size_type_basic_json}
 
-  This function returns true iff the JSON value is a string.
-
-  @return `true` if type is string, `false` otherwise.
+    @since version 1.0.0
+    */
+    basic_json(size_type cnt, const basic_json &val) : m_type(value_t::array)
+    {
+        m_value.array = create<array_t>(cnt, val);
+        assert_invariant();
+    }
 
-  @complexity Constant.
+    /*!
+    @brief construct a JSON container given an iterator range
 
-  @exceptionsafety No-throw guarantee: this member function never throws
-  exceptions.
+    Constructs the JSON value with the contents of the range `[first, last)`.
+    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.
+    - 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.
 
-  @liveexample{The following code exemplifies `is_string()` for all JSON
-  types.,is_string}
+    @tparam InputIT an input iterator type (@ref iterator or @ref
+    const_iterator)
 
-  @since version 1.0.0
-  */
-  constexpr bool is_string() const noexcept
-  {
-    return m_type == value_t::string;
-  }
+    @param[in] first begin of the range to copy from (included)
+    @param[in] last end of the range to copy from (excluded)
 
-  /*!
-  @brief return whether value is discarded
+    @pre Iterators @a first and @a last must be initialized. **This
+         precondition is enforced with an assertion.**
 
-  This function returns true iff the JSON value was discarded during parsing
-  with a callback function (see @ref parser_callback_t).
+    @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"`
 
-  @note This function will always be `false` for JSON values after parsing.
-  That is, discarded values can only occur during parsing, but will be
-  removed when inside a structured value or replaced by null in other cases.
+    @complexity Linear in distance between @a first and @a last.
 
-  @return `true` if type is discarded, `false` otherwise.
+    @liveexample{The example below shows several ways to create JSON values by
+    specifying a subrange with iterators.,basic_json__InputIt_InputIt}
 
-  @complexity Constant.
+    @since version 1.0.0
+    */
+    template <
+        class InputIT,
+        typename std::enable_if<
+            std::is_same<InputIT, typename basic_json_t::iterator>::value or
+                std::is_same<InputIT,
+                             typename basic_json_t::const_iterator>::value,
+            int>::type = 0>
+    basic_json(InputIT first, InputIT last)
+    {
+        assert(first.m_object != nullptr);
+        assert(last.m_object != nullptr);
 
-  @exceptionsafety No-throw guarantee: this member function never throws
-  exceptions.
+        // make sure iterator fits the current value
+        if (first.m_object != last.m_object)
+        {
+            JSON_THROW(std::domain_error("iterators are not compatible"));
+        }
 
-  @liveexample{The following code exemplifies `is_discarded()` for all JSON
-  types.,is_discarded}
+        // copy type from first iterator
+        m_type = first.m_object->m_type;
 
-  @since version 1.0.0
-  */
-  constexpr bool is_discarded() const noexcept
-  {
-    return m_type == value_t::discarded;
-  }
+        // check if iterator range is complete for primitive values
+        switch (m_type)
+        {
+        case value_t::boolean:
+        case value_t::number_float:
+        case value_t::number_integer:
+        case value_t::number_unsigned:
+        case value_t::string:
+        {
+            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"));
+            }
+            break;
+        }
 
-  /*!
-  @brief return the type of the JSON value (implicit)
+        default:
+        {
+            break;
+        }
+        }
 
-  Implicitly return the type of the JSON value as a value from the @ref
-  value_t enumeration.
+        switch (m_type)
+        {
+        case value_t::number_integer:
+        {
+            m_value.number_integer = first.m_object->m_value.number_integer;
+            break;
+        }
 
-  @return the type of the JSON value
+        case value_t::number_unsigned:
+        {
+            m_value.number_unsigned = first.m_object->m_value.number_unsigned;
+            break;
+        }
 
-  @complexity Constant.
+        case value_t::number_float:
+        {
+            m_value.number_float = first.m_object->m_value.number_float;
+            break;
+        }
 
-  @exceptionsafety No-throw guarantee: this member function never throws
-  exceptions.
+        case value_t::boolean:
+        {
+            m_value.boolean = first.m_object->m_value.boolean;
+            break;
+        }
 
-  @liveexample{The following code exemplifies the @ref value_t operator for
-  all JSON types.,operator__value_t}
+        case value_t::string:
+        {
+            m_value = *first.m_object->m_value.string;
+            break;
+        }
 
-  @since version 1.0.0
-  */
-  constexpr operator value_t() const noexcept { return m_type; }
+        case value_t::object:
+        {
+            m_value.object = create<object_t>(first.m_it.object_iterator,
+                                              last.m_it.object_iterator);
+            break;
+        }
 
-  /// @}
+        case value_t::array:
+        {
+            m_value.array = create<array_t>(first.m_it.array_iterator,
+                                            last.m_it.array_iterator);
+            break;
+        }
 
-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());
-    }
+        default:
+        {
+            JSON_THROW(
+                std::domain_error("cannot use construct with iterators from " +
+                                  first.m_object->type_name()));
+        }
+        }
 
-    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;
+        assert_invariant();
     }
 
-    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);
-      }
-
-      case value_t::number_float:
-      {
-        return static_cast<T>(m_value.number_float);
-      }
-
-      default:
-      {
-        JSON_THROW(
-            std::domain_error("type must be number, but is " + type_name()));
-      }
-    }
-  }
-
-  /// get a boolean (explicit)
-  boolean_t get_impl(boolean_t * /*unused*/) const
-  {
-    if (is_boolean())
-    {
-      return m_value.boolean;
-    }
-    else
-    {
-      JSON_THROW(
-          std::domain_error("type must be boolean, but is " + type_name()));
-    }
-  }
-
-  /// get a pointer to the value (object)
-  object_t *get_impl_ptr(object_t * /*unused*/) noexcept
-  {
-    return is_object() ? m_value.object : nullptr;
-  }
-
-  /// get a pointer to the value (object)
-  constexpr const object_t *get_impl_ptr(const object_t * /*unused*/) const
-      noexcept
-  {
-    return is_object() ? m_value.object : nullptr;
-  }
-
-  /// get a pointer to the value (array)
-  array_t *get_impl_ptr(array_t * /*unused*/) noexcept
-  {
-    return is_array() ? m_value.array : nullptr;
-  }
-
-  /// get a pointer to the value (array)
-  constexpr const array_t *get_impl_ptr(const array_t * /*unused*/) const
-      noexcept
-  {
-    return is_array() ? m_value.array : nullptr;
-  }
-
-  /// get a pointer to the value (string)
-  string_t *get_impl_ptr(string_t * /*unused*/) noexcept
-  {
-    return is_string() ? m_value.string : nullptr;
-  }
-
-  /// get a pointer to the value (string)
-  constexpr const string_t *get_impl_ptr(const string_t * /*unused*/) const
-      noexcept
-  {
-    return is_string() ? m_value.string : nullptr;
-  }
-
-  /// get a pointer to the value (boolean)
-  boolean_t *get_impl_ptr(boolean_t * /*unused*/) noexcept
-  {
-    return is_boolean() ? &m_value.boolean : nullptr;
-  }
-
-  /// get a pointer to the value (boolean)
-  constexpr const boolean_t *get_impl_ptr(const boolean_t * /*unused*/) const
-      noexcept
-  {
-    return is_boolean() ? &m_value.boolean : nullptr;
-  }
-
-  /// get a pointer to the value (integer number)
-  number_integer_t *get_impl_ptr(number_integer_t * /*unused*/) noexcept
-  {
-    return is_number_integer() ? &m_value.number_integer : nullptr;
-  }
-
-  /// get a pointer to the value (integer number)
-  constexpr const number_integer_t *
-  get_impl_ptr(const number_integer_t * /*unused*/) const noexcept
-  {
-    return is_number_integer() ? &m_value.number_integer : nullptr;
-  }
-
-  /// get a pointer to the value (unsigned number)
-  number_unsigned_t *get_impl_ptr(number_unsigned_t * /*unused*/) noexcept
-  {
-    return is_number_unsigned() ? &m_value.number_unsigned : nullptr;
-  }
-
-  /// get a pointer to the value (unsigned number)
-  constexpr const number_unsigned_t *
-  get_impl_ptr(const number_unsigned_t * /*unused*/) const noexcept
-  {
-    return is_number_unsigned() ? &m_value.number_unsigned : nullptr;
-  }
-
-  /// get a pointer to the value (floating-point number)
-  number_float_t *get_impl_ptr(number_float_t * /*unused*/) noexcept
-  {
-    return is_number_float() ? &m_value.number_float : nullptr;
-  }
-
-  /// get a pointer to the value (floating-point number)
-  constexpr const number_float_t *
-  get_impl_ptr(const number_float_t * /*unused*/) const noexcept
-  {
-    return is_number_float() ? &m_value.number_float : nullptr;
-  }
-
-  /*!
-  @brief helper function to implement get_ref()
-
-  This funcion helps to implement get_ref() without code duplication for
-  const and non-const overloads
-
-  @tparam ThisType will be deduced as `basic_json` or `const basic_json`
-
-  @throw std::domain_error if ReferenceType does not match underlying value
-  type of the current JSON
-  */
-  template <typename ReferenceType, typename ThisType>
-  static ReferenceType get_ref_impl(ThisType &obj)
-  {
-    // helper type
-    using PointerType = typename std::add_pointer<ReferenceType>::type;
-
-    // delegate the call to get_ptr<>()
-    auto ptr = obj.template get_ptr<PointerType>();
-
-    if (ptr != nullptr)
-    {
-      return *ptr;
-    }
-
-    throw std::domain_error(
-        "incompatible ReferenceType for get_ref, actual type is " +
-        obj.type_name());
-  }
-
-public:
-  /// @name value access
-  /// Direct access to the stored value of a JSON value.
-  /// @{
-
-  /*!
-  @brief get a value (explicit)
-
-  Explicit type conversion between the JSON value and a compatible value.
-
-  @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
-
-  @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; example: `"type must be object, but is null"`
-
-  @complexity Linear in the size of the JSON value.
-
-  @liveexample{The example below shows several conversions from JSON values
-  to other types. There a few things to note: (1) Floating-point numbers can
-  be converted to integers\, (2) A JSON array can be converted to a standard
-  `std::vector<short>`\, (3) A JSON object can be converted to C++
-  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
-
-  @sa @ref operator ValueType() const for implicit conversion
-  @sa @ref get() for pointer-member access
-
-  @since version 1.0.0
-  */
-  template <typename ValueType,
-            typename std::enable_if<not std::is_pointer<ValueType>::value,
-                                    int>::type = 0>
-  ValueType get() const
-  {
-    return get_impl(static_cast<ValueType *>(nullptr));
-  }
-
-  /*!
-  @brief get a pointer value (explicit)
-
-  Explicit pointer access to the internally stored JSON value. No copies are
-  made.
-
-  @warning The pointer becomes invalid if the underlying JSON object
-  changes.
-
-  @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref
-  object_t, @ref string_t, @ref boolean_t, @ref number_integer_t,
-  @ref number_unsigned_t, or @ref number_float_t.
-
-  @return pointer to the internally stored JSON value if the requested
-  pointer type @a PointerType fits to the JSON value; `nullptr` otherwise
-
-  @complexity Constant.
-
-  @liveexample{The example below shows how pointers to internal values of a
-  JSON value can be requested. Note that no type conversions are made and a
-  `nullptr` is returned if the value and the requested pointer type does not
-  match.,get__PointerType}
-
-  @sa @ref get_ptr() for explicit pointer-member access
-
-  @since version 1.0.0
-  */
-  template <typename PointerType,
-            typename std::enable_if<std::is_pointer<PointerType>::value,
-                                    int>::type = 0>
-  PointerType get() noexcept
-  {
-    // delegate the call to get_ptr
-    return get_ptr<PointerType>();
-  }
-
-  /*!
-  @brief get a pointer value (explicit)
-  @copydoc get()
-  */
-  template <typename PointerType,
-            typename std::enable_if<std::is_pointer<PointerType>::value,
-                                    int>::type = 0>
-  constexpr const PointerType get() const noexcept
-  {
-    // delegate the call to get_ptr
-    return get_ptr<PointerType>();
-  }
-
-  /*!
-  @brief get a pointer value (implicit)
-
-  Implicit pointer access to the internally stored JSON value. No copies are
-  made.
-
-  @warning Writing data to the pointee of the result yields an undefined
-  state.
-
-  @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref
-  object_t, @ref string_t, @ref boolean_t, @ref number_integer_t,
-  @ref number_unsigned_t, or @ref number_float_t. Enforced by a static
-  assertion.
-
-  @return pointer to the internally stored JSON value if the requested
-  pointer type @a PointerType fits to the JSON value; `nullptr` otherwise
-
-  @complexity Constant.
-
-  @liveexample{The example below shows how pointers to internal values of a
-  JSON value can be requested. Note that no type conversions are made and a
-  `nullptr` is returned if the value and the requested pointer type does not
-  match.,get_ptr}
-
-  @since version 1.0.0
-  */
-  template <typename PointerType,
-            typename std::enable_if<std::is_pointer<PointerType>::value,
-                                    int>::type = 0>
-  PointerType get_ptr() noexcept
-  {
-    // get the type of the PointerType (remove pointer and const)
-    using pointee_t = typename std::remove_const<typename std::remove_pointer<
-        typename std::remove_const<PointerType>::type>::type>::type;
-    // make sure the type matches the allowed types
-    static_assert(std::is_same<object_t, pointee_t>::value or
-                      std::is_same<array_t, pointee_t>::value or
-                      std::is_same<string_t, pointee_t>::value or
-                      std::is_same<boolean_t, pointee_t>::value or
-                      std::is_same<number_integer_t, pointee_t>::value or
-                      std::is_same<number_unsigned_t, pointee_t>::value or
-                      std::is_same<number_float_t, pointee_t>::value,
-                  "incompatible pointer type");
-
-    // delegate the call to get_impl_ptr<>()
-    return get_impl_ptr(static_cast<PointerType>(nullptr));
-  }
-
-  /*!
-  @brief get a pointer value (implicit)
-  @copydoc get_ptr()
-  */
-  template <
-      typename PointerType,
-      typename std::enable_if<std::is_pointer<PointerType>::value and
-                                  std::is_const<typename std::remove_pointer<
-                                      PointerType>::type>::value,
-                              int>::type = 0>
-  constexpr const PointerType get_ptr() const noexcept
-  {
-    // get the type of the PointerType (remove pointer and const)
-    using pointee_t = typename std::remove_const<typename std::remove_pointer<
-        typename std::remove_const<PointerType>::type>::type>::type;
-    // make sure the type matches the allowed types
-    static_assert(std::is_same<object_t, pointee_t>::value or
-                      std::is_same<array_t, pointee_t>::value or
-                      std::is_same<string_t, pointee_t>::value or
-                      std::is_same<boolean_t, pointee_t>::value or
-                      std::is_same<number_integer_t, pointee_t>::value or
-                      std::is_same<number_unsigned_t, pointee_t>::value or
-                      std::is_same<number_float_t, pointee_t>::value,
-                  "incompatible pointer type");
-
-    // delegate the call to get_impl_ptr<>() const
-    return get_impl_ptr(static_cast<const PointerType>(nullptr));
-  }
-
-  /*!
-  @brief get a reference value (implicit)
-
-  Implict reference access to the internally stored JSON value. No copies
-  are made.
-
-  @warning Writing data to the referee of the result yields an undefined
-  state.
-
-  @tparam ReferenceType reference type; must be a reference to @ref array_t,
-  @ref object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, or
-  @ref number_float_t. Enforced by static assertion.
-
-  @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
-
-  @throw std::domain_error in case passed type @a ReferenceType is
-  incompatible with the stored JSON value
-
-  @complexity Constant.
-
-  @liveexample{The example shows several calls to `get_ref()`.,get_ref}
-
-  @since version 1.1.0
-  */
-  template <typename ReferenceType,
-            typename std::enable_if<std::is_reference<ReferenceType>::value,
-                                    int>::type = 0>
-  ReferenceType get_ref()
-  {
-    // delegate call to get_ref_impl
-    return get_ref_impl<ReferenceType>(*this);
-  }
-
-  /*!
-  @brief get a reference value (implicit)
-  @copydoc get_ref()
-  */
-  template <
-      typename ReferenceType,
-      typename std::enable_if<std::is_reference<ReferenceType>::value and
-                                  std::is_const<typename std::remove_reference<
-                                      ReferenceType>::type>::value,
-                              int>::type = 0>
-  ReferenceType get_ref() const
-  {
-    // delegate call to get_ref_impl
-    return get_ref_impl<ReferenceType>(*this);
-  }
-
-  /*!
-  @brief get a value (implicit)
-
-  Implicit type conversion between the JSON value and a compatible value.
-  The call is realized by calling @ref get() const.
-
-  @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 character type of @ref string_t
-  as well as an initializer list of this type is excluded to avoid
-  ambiguities as these types implicitly convert to `std::string`.
-
-  @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
-
-  @complexity Linear in the size of the JSON value.
-
-  @liveexample{The example below shows several conversions from JSON values
-  to other types. There a few things to note: (1) Floating-point numbers can
-  be converted to integers\, (2) A JSON array can be converted to a standard
-  `std::vector<short>`\, (3) A JSON object can be converted to C++
-  associative containers such as `std::unordered_map<std::string\,
-  json>`.,operator__ValueType}
-
-  @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
-#endif
-          ,
-          int>::type = 0>
-  operator ValueType() const
-  {
-    // delegate the call to get<>() const
-    return get<ValueType>();
-  }
-
-  /// @}
-
-  ////////////////////
-  // element access //
-  ////////////////////
-
-  /// @name element access
-  /// Access to the JSON value.
-  /// @{
-
-  /*!
-  @brief access specified array element with bounds checking
-
-  Returns a reference to the element at specified location @a idx, with
-  bounds checking.
+    /*!
+    @brief construct a JSON value given an input stream
 
-  @param[in] idx  index of the element to access
+    @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)
 
-  @return reference to the element at index @a idx
+    @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.
 
-  @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"`
+    @note A UTF-8 byte order mark is silently ignored.
 
-  @complexity Constant.
+    @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 shows how array elements can be read and
-  written using `at()`.,at__size_type}
+    @liveexample{The example below demonstrates constructing a JSON value from
+    a `std::stringstream` with and without callback
+    function.,basic_json__istream}
 
-  @since version 1.0.0
-  */
-  reference at(size_type idx)
-  {
-    // at only works for arrays
-    if (is_array())
-    {
-      JSON_TRY { return m_value.array->at(idx); }
-      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"));
-      }
-    }
-    else
+    @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)
     {
-      JSON_THROW(std::domain_error("cannot use at() with " + type_name()));
+        *this = parser(i, cb).parse();
+        assert_invariant();
     }
-  }
 
-  /*!
-  @brief access specified array element with bounds checking
+    ///////////////////////////////////////
+    // other constructors and destructor //
+    ///////////////////////////////////////
+
+    /*!
+    @brief copy constructor
 
-  Returns a const reference to the element at specified location @a idx,
-  with bounds checking.
+    Creates a copy of a given JSON value.
 
-  @param[in] idx  index of the element to access
+    @param[in] other  the JSON value to copy
 
-  @return const reference to the element at index @a idx
+    @complexity Linear in the size of @a other.
 
-  @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"`
+    @requirement This function helps `basic_json` satisfying the
+    [Container](http://en.cppreference.com/w/cpp/concept/Container)
+    requirements:
+    - The complexity is linear.
+    - As postcondition, it holds: `other == basic_json(other)`.
 
-  @complexity Constant.
+    @throw std::bad_alloc if allocation for object, array, or string fails.
 
-  @liveexample{The example below shows how array elements can be read using
-  `at()`.,at__size_type_const}
+    @liveexample{The following code shows an example for the copy
+    constructor.,basic_json__basic_json}
 
-  @since version 1.0.0
-  */
-  const_reference at(size_type idx) const
-  {
-    // at only works for arrays
-    if (is_array())
-    {
-      JSON_TRY { return m_value.array->at(idx); }
-      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"));
-      }
-    }
-    else
+    @since version 1.0.0
+    */
+    basic_json(const basic_json &other) : m_type(other.m_type)
     {
-      JSON_THROW(std::domain_error("cannot use at() with " + type_name()));
-    }
-  }
+        // check of passed value is valid
+        other.assert_invariant();
 
-  /*!
-  @brief access specified object element with bounds checking
+        switch (m_type)
+        {
+        case value_t::object:
+        {
+            m_value = *other.m_value.object;
+            break;
+        }
 
-  Returns a reference to the element at with specified key @a key, with
-  bounds checking.
+        case value_t::array:
+        {
+            m_value = *other.m_value.array;
+            break;
+        }
 
-  @param[in] key  key of the element to access
+        case value_t::string:
+        {
+            m_value = *other.m_value.string;
+            break;
+        }
 
-  @return reference to the element at key @a key
+        case value_t::boolean:
+        {
+            m_value = other.m_value.boolean;
+            break;
+        }
 
-  @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"`
+        case value_t::number_integer:
+        {
+            m_value = other.m_value.number_integer;
+            break;
+        }
 
-  @complexity Logarithmic in the size of the container.
+        case value_t::number_unsigned:
+        {
+            m_value = other.m_value.number_unsigned;
+            break;
+        }
 
-  @liveexample{The example below shows how object elements can be read and
-  written using `at()`.,at__object_t_key_type}
+        case value_t::number_float:
+        {
+            m_value = other.m_value.number_float;
+            break;
+        }
 
-  @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
+        default:
+        {
+            break;
+        }
+        }
 
-  @since version 1.0.0
-  */
-  reference at(const typename object_t::key_type &key)
-  {
-    // at only works for objects
-    if (is_object())
-    {
-      JSON_TRY { return m_value.object->at(key); }
-      JSON_CATCH(std::out_of_range &)
-      {
-        // create better exception explanation
-        JSON_THROW(std::out_of_range("key '" + key + "' not found"));
-      }
-    }
-    else
-    {
-      JSON_THROW(std::domain_error("cannot use at() with " + type_name()));
+        assert_invariant();
     }
-  }
 
-  /*!
-  @brief access specified object element with bounds checking
-
-  Returns a const reference to the element at with specified key @a key,
-  with bounds checking.
-
-  @param[in] key  key of the element to access
+    /*!
+    @brief move constructor
 
-  @return const reference to the element at key @a key
+    Move constructor. Constructs a JSON value with the contents of the given
+    value @a other using move semantics. It "steals" the resources from @a
+    other and leaves it as JSON null value.
 
-  @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"`
+    @param[in,out] other  value to move to this object
 
-  @complexity Logarithmic in the size of the container.
+    @post @a other is a JSON null value
 
-  @liveexample{The example below shows how object elements can be read using
-  `at()`.,at__object_t_key_type_const}
+    @complexity Constant.
 
-  @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
+    @liveexample{The code below shows the move constructor explicitly called
+    via std::move.,basic_json__moveconstructor}
 
-  @since version 1.0.0
-  */
-  const_reference at(const typename object_t::key_type &key) const
-  {
-    // at only works for objects
-    if (is_object())
-    {
-      JSON_TRY { return m_value.object->at(key); }
-      JSON_CATCH(std::out_of_range &)
-      {
-        // create better exception explanation
-        JSON_THROW(std::out_of_range("key '" + key + "' not found"));
-      }
-    }
-    else
+    @since version 1.0.0
+    */
+    basic_json(basic_json &&other) noexcept : m_type(std::move(other.m_type)),
+                                              m_value(std::move(other.m_value))
     {
-      JSON_THROW(std::domain_error("cannot use at() with " + type_name()));
-    }
-  }
+        // check that passed value is valid
+        other.assert_invariant();
 
-  /*!
-  @brief access specified array element
+        // invalidate payload
+        other.m_type = value_t::null;
+        other.m_value = {};
 
-  Returns a reference to the element at specified location @a idx.
+        assert_invariant();
+    }
 
-  @note If @a idx is beyond the range of the array (i.e., `idx >= size()`),
-  then the array is silently filled up with `null` values to make `idx` a
-  valid reference to the last stored element.
+    /*!
+    @brief copy assignment
 
-  @param[in] idx  index of the element to access
+    Copy assignment operator. Copies a JSON value via the "copy and swap"
+    strategy: It is expressed in terms of the copy constructor, destructor,
+    and the swap() member function.
 
-  @return reference to the element at index @a idx
+    @param[in] other  value to copy from
 
-  @throw std::domain_error if JSON is not an array or null; example:
-  `"cannot use operator[] with string"`
+    @complexity Linear.
 
-  @complexity Constant if @a idx is in the range of the array. Otherwise
-  linear in `idx - size()`.
+    @requirement This function helps `basic_json` satisfying the
+    [Container](http://en.cppreference.com/w/cpp/concept/Container)
+    requirements:
+    - The complexity is linear.
 
-  @liveexample{The example below shows how array elements can be read and
-  written using `[]` operator. Note the addition of `null`
-  values.,operatorarray__size_type}
+    @liveexample{The code below shows and example for the copy assignment. It
+    creates a copy of value `a` which is then swapped with `b`. Finally\, the
+    copy of `a` (which is the null value after the swap) is
+    destroyed.,basic_json__copyassignment}
 
-  @since version 1.0.0
-  */
-  reference operator[](size_type idx)
-  {
-    // implicitly convert null value to an empty array
-    if (is_null())
+    @since version 1.0.0
+    */
+    reference &operator=(basic_json other) noexcept(
+        std::is_nothrow_move_constructible<value_t>::value
+            and std::is_nothrow_move_assignable<value_t>::value
+                and std::is_nothrow_move_constructible<json_value>::value
+                    and std::is_nothrow_move_assignable<json_value>::value)
     {
-      m_type = value_t::array;
-      m_value.array = create<array_t>();
-      assert_invariant();
-    }
+        // check that passed value is valid
+        other.assert_invariant();
 
-    // operator[] only works for arrays
-    if (is_array())
-    {
-      // fill up array with null values if given idx is outside range
-      if (idx >= m_value.array->size())
-      {
-        m_value.array->insert(m_value.array->end(),
-                              idx - m_value.array->size() + 1, basic_json());
-      }
+        using std::swap;
+        swap(m_type, other.m_type);
+        swap(m_value, other.m_value);
 
-      return m_value.array->operator[](idx);
+        assert_invariant();
+        return *this;
     }
 
-    JSON_THROW(std::domain_error("cannot use operator[] with " + type_name()));
-  }
+    /*!
+    @brief destructor
 
-  /*!
-  @brief access specified array element
+    Destroys the JSON value and frees all allocated memory.
 
-  Returns a const reference to the element at specified location @a idx.
+    @complexity Linear.
 
-  @param[in] idx  index of the element to access
+    @requirement This function helps `basic_json` satisfying the
+    [Container](http://en.cppreference.com/w/cpp/concept/Container)
+    requirements:
+    - The complexity is linear.
+    - All stored elements are destroyed and all memory is freed.
 
-  @return const reference to the element at index @a idx
+    @since version 1.0.0
+    */
+    ~basic_json()
+    {
+        assert_invariant();
 
-  @throw std::domain_error if JSON is not an array; example: `"cannot use
-  operator[] with null"`
+        switch (m_type)
+        {
+        case value_t::object:
+        {
+            AllocatorType<object_t> alloc;
+            alloc.destroy(m_value.object);
+            alloc.deallocate(m_value.object, 1);
+            break;
+        }
 
-  @complexity Constant.
+        case value_t::array:
+        {
+            AllocatorType<array_t> alloc;
+            alloc.destroy(m_value.array);
+            alloc.deallocate(m_value.array, 1);
+            break;
+        }
 
-  @liveexample{The example below shows how array elements can be read using
-  the `[]` operator.,operatorarray__size_type_const}
+        case value_t::string:
+        {
+            AllocatorType<string_t> alloc;
+            alloc.destroy(m_value.string);
+            alloc.deallocate(m_value.string, 1);
+            break;
+        }
 
-  @since version 1.0.0
-  */
-  const_reference operator[](size_type idx) const
-  {
-    // const operator[] only works for arrays
-    if (is_array())
-    {
-      return m_value.array->operator[](idx);
+        default:
+        {
+            // all other types need no specific destructor
+            break;
+        }
+        }
     }
 
-    JSON_THROW(std::domain_error("cannot use operator[] with " + type_name()));
-  }
+    /// @}
 
-  /*!
-  @brief access specified object element
+public:
+    ///////////////////////
+    // object inspection //
+    ///////////////////////
 
-  Returns a reference to the element at with specified key @a key.
+    /// @name object inspection
+    /// Functions to inspect the type of a JSON value.
+    /// @{
 
-  @note If @a key is not found in the object, then it is silently added to
-  the object and filled with a `null` value to make `key` a valid reference.
-  In case the value was `null` before, it is converted to an object.
+    /*!
+    @brief serialization
 
-  @param[in] key  key of the element to access
+    Serialization function for JSON values. The function tries to mimic
+    Python's `json.dumps()` function, and currently supports its @a indent
+    parameter.
 
-  @return reference to the element at key @a key
+    @param[in] indent If indent is nonnegative, then array elements and object
+    members will be pretty-printed with that indent level. An indent level of
+    `0` will only insert newlines. `-1` (the default) selects the most compact
+    representation.
 
-  @throw std::domain_error if JSON is not an object or null; example:
-  `"cannot use operator[] with string"`
+    @return string containing the serialization of the JSON value
 
-  @complexity Logarithmic in the size of the container.
+    @complexity Linear.
 
-  @liveexample{The example below shows how object elements can be read and
-  written using the `[]` operator.,operatorarray__key_type}
+    @liveexample{The following example shows the effect of different @a indent
+    parameters to the result of the serialization.,dump}
 
-  @sa @ref at(const typename object_t::key_type&) for access by reference
-  with range checking
-  @sa @ref value() for access by value with a default value
+    @see https://docs.python.org/2/library/json.html#json.dump
 
-  @since version 1.0.0
-  */
-  reference operator[](const typename object_t::key_type &key)
-  {
-    // implicitly convert null value to an empty object
-    if (is_null())
+    @since version 1.0.0
+    */
+    string_t dump(const int indent = -1) const
     {
-      m_type = value_t::object;
-      m_value.object = create<object_t>();
-      assert_invariant();
-    }
+        std::stringstream ss;
+        // fix locale problems
+        ss.imbue(std::locale::classic());
 
-    // operator[] only works for objects
-    if (is_object())
-    {
-      return m_value.object->operator[](key);
+        // 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);
+
+        if (indent >= 0)
+        {
+            dump(ss, true, static_cast<unsigned int>(indent));
+        }
+        else
+        {
+            dump(ss, false, 0);
+        }
+
+        return ss.str();
     }
 
-    JSON_THROW(std::domain_error("cannot use operator[] with " + type_name()));
-  }
+    /*!
+    @brief return the type of the JSON value (explicit)
 
-  /*!
-  @brief read-only access specified object element
+    Return the type of the JSON value as a value from the @ref value_t
+    enumeration.
 
-  Returns a const reference to the element at with specified key @a key. No
-  bounds checking is performed.
+    @return the type of the JSON value
 
-  @warning If the element with key @a key does not exist, the behavior is
-  undefined.
+    @complexity Constant.
 
-  @param[in] key  key of the element to access
+    @exceptionsafety No-throw guarantee: this member function never throws
+    exceptions.
 
-  @return const reference to the element at key @a key
+    @liveexample{The following code exemplifies `type()` for all JSON
+    types.,type}
 
-  @pre The element with key @a key must exist. **This precondition is
-       enforced with an assertion.**
+    @since version 1.0.0
+    */
+    constexpr value_t type() const noexcept { return m_type; }
 
-  @throw std::domain_error if JSON is not an object; example: `"cannot use
-  operator[] with null"`
+    /*!
+    @brief return whether type is primitive
 
-  @complexity Logarithmic in the size of the container.
+    This function returns true iff the JSON type is primitive (string, number,
+    boolean, or null).
 
-  @liveexample{The example below shows how object elements can be read using
-  the `[]` operator.,operatorarray__key_type_const}
+    @return `true` if type is primitive (string, number, boolean, or null),
+    `false` otherwise.
 
-  @sa @ref at(const typename object_t::key_type&) for access by reference
-  with range checking
-  @sa @ref value() for access by value with a default value
+    @complexity Constant.
 
-  @since version 1.0.0
-  */
-  const_reference operator[](const typename object_t::key_type &key) const
-  {
-    // const operator[] only works for objects
-    if (is_object())
-    {
-      assert(m_value.object->find(key) != m_value.object->end());
-      return m_value.object->find(key)->second;
-    }
+    @exceptionsafety No-throw guarantee: this member function never throws
+    exceptions.
 
-    JSON_THROW(std::domain_error("cannot use operator[] with " + type_name()));
-  }
+    @liveexample{The following code exemplifies `is_primitive()` for all JSON
+    types.,is_primitive}
 
-  /*!
-  @brief access specified object element
+    @sa @ref is_structured() -- returns whether JSON value is structured
+    @sa @ref is_null() -- returns whether JSON value is `null`
+    @sa @ref is_string() -- returns whether JSON value is a string
+    @sa @ref is_boolean() -- returns whether JSON value is a boolean
+    @sa @ref is_number() -- returns whether JSON value is a number
 
-  Returns a reference to the element at with specified key @a key.
+    @since version 1.0.0
+    */
+    constexpr bool is_primitive() const noexcept
+    {
+        return is_null() or is_string() or is_boolean() or is_number();
+    }
 
-  @note If @a key is not found in the object, then it is silently added to
-  the object and filled with a `null` value to make `key` a valid reference.
-  In case the value was `null` before, it is converted to an object.
+    /*!
+    @brief return whether type is structured
 
-  @param[in] key  key of the element to access
+    This function returns true iff the JSON type is structured (array or
+    object).
 
-  @return reference to the element at key @a key
+    @return `true` if type is structured (array or object), `false` otherwise.
 
-  @throw std::domain_error if JSON is not an object or null; example:
-  `"cannot use operator[] with string"`
+    @complexity Constant.
 
-  @complexity Logarithmic in the size of the container.
+    @exceptionsafety No-throw guarantee: this member function never throws
+    exceptions.
 
-  @liveexample{The example below shows how object elements can be read and
-  written using the `[]` operator.,operatorarray__key_type}
+    @liveexample{The following code exemplifies `is_structured()` for all JSON
+    types.,is_structured}
 
-  @sa @ref at(const typename object_t::key_type&) for access by reference
-  with range checking
-  @sa @ref value() for access by value with a default value
+    @sa @ref is_primitive() -- returns whether value is primitive
+    @sa @ref is_array() -- returns whether value is an array
+    @sa @ref is_object() -- returns whether value is an object
 
-  @since version 1.0.0
-  */
-  template <typename T, std::size_t n> reference operator[](T *(&key)[n])
-  {
-    return operator[](static_cast<const T>(key));
-  }
+    @since version 1.0.0
+    */
+    constexpr bool is_structured() const noexcept
+    {
+        return is_array() or is_object();
+    }
 
-  /*!
-  @brief read-only access specified object element
+    /*!
+    @brief return whether value is null
 
-  Returns a const reference to the element at with specified key @a key. No
-  bounds checking is performed.
+    This function returns true iff the JSON value is null.
 
-  @warning If the element with key @a key does not exist, the behavior is
-  undefined.
+    @return `true` if type is null, `false` otherwise.
 
-  @note This function is required for compatibility reasons with Clang.
+    @complexity Constant.
 
-  @param[in] key  key of the element to access
+    @exceptionsafety No-throw guarantee: this member function never throws
+    exceptions.
 
-  @return const reference to the element at key @a key
+    @liveexample{The following code exemplifies `is_null()` for all JSON
+    types.,is_null}
 
-  @throw std::domain_error if JSON is not an object; example: `"cannot use
-  operator[] with null"`
+    @since version 1.0.0
+    */
+    constexpr bool is_null() const noexcept { return m_type == value_t::null; }
 
-  @complexity Logarithmic in the size of the container.
+    /*!
+    @brief return whether value is a boolean
 
-  @liveexample{The example below shows how object elements can be read using
-  the `[]` operator.,operatorarray__key_type_const}
+    This function returns true iff the JSON value is a boolean.
 
-  @sa @ref at(const typename object_t::key_type&) for access by reference
-  with range checking
-  @sa @ref value() for access by value with a default value
+    @return `true` if type is boolean, `false` otherwise.
 
-  @since version 1.0.0
-  */
-  template <typename T, std::size_t n>
-  const_reference operator[](T *(&key)[n]) const
-  {
-    return operator[](static_cast<const T>(key));
-  }
+    @complexity Constant.
 
-  /*!
-  @brief access specified object element
+    @exceptionsafety No-throw guarantee: this member function never throws
+    exceptions.
 
-  Returns a reference to the element at with specified key @a key.
+    @liveexample{The following code exemplifies `is_boolean()` for all JSON
+    types.,is_boolean}
 
-  @note If @a key is not found in the object, then it is silently added to
-  the object and filled with a `null` value to make `key` a valid reference.
-  In case the value was `null` before, it is converted to an object.
+    @since version 1.0.0
+    */
+    constexpr bool is_boolean() const noexcept
+    {
+        return m_type == value_t::boolean;
+    }
 
-  @param[in] key  key of the element to access
+    /*!
+    @brief return whether value is a number
 
-  @return reference to the element at key @a key
+    This function returns true iff the JSON value is a number. This includes
+    both integer and floating-point values.
 
-  @throw std::domain_error if JSON is not an object or null; example:
-  `"cannot use operator[] with string"`
+    @return `true` if type is number (regardless whether integer, unsigned
+    integer or floating-type), `false` otherwise.
 
-  @complexity Logarithmic in the size of the container.
+    @complexity Constant.
 
-  @liveexample{The example below shows how object elements can be read and
-  written using the `[]` operator.,operatorarray__key_type}
+    @exceptionsafety No-throw guarantee: this member function never throws
+    exceptions.
 
-  @sa @ref at(const typename object_t::key_type&) for access by reference
-  with range checking
-  @sa @ref value() for access by value with a default value
+    @liveexample{The following code exemplifies `is_number()` for all JSON
+    types.,is_number}
 
-  @since version 1.1.0
-  */
-  template <typename T> reference operator[](T *key)
-  {
-    // implicitly convert null to object
-    if (is_null())
-    {
-      m_type = value_t::object;
-      m_value = value_t::object;
-      assert_invariant();
-    }
+    @sa @ref is_number_integer() -- check if value is an integer or unsigned
+    integer number
+    @sa @ref is_number_unsigned() -- check if value is an unsigned integer
+    number
+    @sa @ref is_number_float() -- check if value is a floating-point number
 
-    // at only works for objects
-    if (is_object())
+    @since version 1.0.0
+    */
+    constexpr bool is_number() const noexcept
     {
-      return m_value.object->operator[](key);
+        return is_number_integer() or is_number_float();
     }
 
-    JSON_THROW(std::domain_error("cannot use operator[] with " + type_name()));
-  }
-
-  /*!
-  @brief read-only access specified object element
-
-  Returns a const reference to the element at with specified key @a key. No
-  bounds checking is performed.
-
-  @warning If the element with key @a key does not exist, the behavior is
-  undefined.
-
-  @param[in] key  key of the element to access
+    /*!
+    @brief return whether value is an integer number
 
-  @return const reference to the element at key @a key
+    This function returns true iff the JSON value is an integer or unsigned
+    integer number. This excludes floating-point values.
 
-  @pre The element with key @a key must exist. **This precondition is
-       enforced with an assertion.**
+    @return `true` if type is an integer or unsigned integer number, `false`
+    otherwise.
 
-  @throw std::domain_error if JSON is not an object; example: `"cannot use
-  operator[] with null"`
+    @complexity Constant.
 
-  @complexity Logarithmic in the size of the container.
+    @exceptionsafety No-throw guarantee: this member function never throws
+    exceptions.
 
-  @liveexample{The example below shows how object elements can be read using
-  the `[]` operator.,operatorarray__key_type_const}
+    @liveexample{The following code exemplifies `is_number_integer()` for all
+    JSON types.,is_number_integer}
 
-  @sa @ref at(const typename object_t::key_type&) for access by reference
-  with range checking
-  @sa @ref value() for access by value with a default value
+    @sa @ref is_number() -- check if value is a number
+    @sa @ref is_number_unsigned() -- check if value is an unsigned integer
+    number
+    @sa @ref is_number_float() -- check if value is a floating-point number
 
-  @since version 1.1.0
-  */
-  template <typename T> const_reference operator[](T *key) const
-  {
-    // at only works for objects
-    if (is_object())
+    @since version 1.0.0
+    */
+    constexpr bool is_number_integer() const noexcept
     {
-      assert(m_value.object->find(key) != m_value.object->end());
-      return m_value.object->find(key)->second;
+        return m_type == value_t::number_integer or
+               m_type == value_t::number_unsigned;
     }
 
-    JSON_THROW(std::domain_error("cannot use operator[] with " + type_name()));
-  }
+    /*!
+    @brief return whether value is an unsigned integer number
+
+    This function returns true iff the JSON value is an unsigned integer
+    number. This excludes floating-point and (signed) integer values.
 
-  /*!
-  @brief access specified object element with default value
+    @return `true` if type is an unsigned integer number, `false` otherwise.
 
-  Returns either a copy of an object's element at the specified key @a key
-  or a given default value if no element with key @a key exists.
+    @complexity Constant.
 
-  The function is basically equivalent to executing
-  @code {.cpp}
-  try {
-      return at(key);
-  } catch(std::out_of_range) {
-      return default_value;
-  }
-  @endcode
+    @exceptionsafety No-throw guarantee: this member function never throws
+    exceptions.
 
-  @note Unlike @ref at(const typename object_t::key_type&), this function
-  does not throw if the given key @a key was not found.
+    @liveexample{The following code exemplifies `is_number_unsigned()` for all
+    JSON types.,is_number_unsigned}
 
-  @note Unlike @ref operator[](const typename object_t::key_type& key), this
-  function does not implicitly add an element to the position defined by @a
-  key. This function is furthermore also applicable to const objects.
+    @sa @ref is_number() -- check if value is a number
+    @sa @ref is_number_integer() -- check if value is an integer or unsigned
+    integer number
+    @sa @ref is_number_float() -- check if value is a floating-point number
 
-  @param[in] key  key of the element to access
-  @param[in] default_value  the value to return if @a key is not found
+    @since version 2.0.0
+    */
+    constexpr bool is_number_unsigned() const noexcept
+    {
+        return m_type == value_t::number_unsigned;
+    }
 
-  @tparam ValueType type compatible to JSON values, for instance `int` for
-  JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for
-  JSON arrays. Note the type of the expected value at @a key and the default
-  value @a default_value must be compatible.
+    /*!
+    @brief return whether value is a floating-point number
 
-  @return copy of the element at key @a key or @a default_value if @a key
-  is not found
+    This function returns true iff the JSON value is a floating-point number.
+    This excludes integer and unsigned integer values.
 
-  @throw std::domain_error if JSON is not an object; example: `"cannot use
-  value() with null"`
+    @return `true` if type is a floating-point number, `false` otherwise.
 
-  @complexity Logarithmic in the size of the container.
+    @complexity Constant.
 
-  @liveexample{The example below shows how object elements can be queried
-  with a default value.,basic_json__value}
+    @exceptionsafety No-throw guarantee: this member function never throws
+    exceptions.
 
-  @sa @ref at(const typename object_t::key_type&) for access by reference
-  with range checking
-  @sa @ref operator[](const typename object_t::key_type&) for unchecked
-  access by reference
+    @liveexample{The following code exemplifies `is_number_float()` for all
+    JSON types.,is_number_float}
 
-  @since version 1.0.0
-  */
-  template <
-      class ValueType,
-      typename std::enable_if<
-          std::is_convertible<basic_json_t, ValueType>::value, int>::type = 0>
-  ValueType value(const typename object_t::key_type &key,
-                  ValueType default_value) const
-  {
-    // at only works for objects
-    if (is_object())
-    {
-      // if key is found, return value and given default value otherwise
-      const auto it = find(key);
-      if (it != end())
-      {
-        return *it;
-      }
+    @sa @ref is_number() -- check if value is number
+    @sa @ref is_number_integer() -- check if value is an integer number
+    @sa @ref is_number_unsigned() -- check if value is an unsigned integer
+    number
 
-      return default_value;
-    }
-    else
+    @since version 1.0.0
+    */
+    constexpr bool is_number_float() const noexcept
     {
-      JSON_THROW(std::domain_error("cannot use value() with " + type_name()));
+        return m_type == value_t::number_float;
     }
-  }
-
-  /*!
-  @brief overload for a default value of type const char*
-  @copydoc basic_json::value(const typename object_t::key_type&, ValueType)
-  const
-  */
-  string_t value(const typename object_t::key_type &key,
-                 const char *default_value) const
-  {
-    return value(key, string_t(default_value));
-  }
 
-  /*!
-  @brief access specified object element via JSON Pointer with default value
-
-  Returns either a copy of an object's element at the specified key @a key
-  or a given default value if no element with key @a key exists.
-
-  The function is basically equivalent to executing
-  @code {.cpp}
-  try {
-      return at(ptr);
-  } catch(std::out_of_range) {
-      return default_value;
-  }
-  @endcode
-
-  @note Unlike @ref at(const json_pointer&), this function does not throw
-  if the given key @a key was not found.
-
-  @param[in] ptr  a JSON pointer to the element to access
-  @param[in] default_value  the value to return if @a ptr found no value
-
-  @tparam ValueType type compatible to JSON values, for instance `int` for
-  JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for
-  JSON arrays. Note the type of the expected value at @a key and the default
-  value @a default_value must be compatible.
+    /*!
+    @brief return whether value is an object
 
-  @return copy of the element at key @a key or @a default_value if @a key
-  is not found
+    This function returns true iff the JSON value is an object.
 
-  @throw std::domain_error if JSON is not an object; example: `"cannot use
-  value() with null"`
+    @return `true` if type is object, `false` otherwise.
 
-  @complexity Logarithmic in the size of the container.
+    @complexity Constant.
 
-  @liveexample{The example below shows how object elements can be queried
-  with a default value.,basic_json__value_ptr}
+    @exceptionsafety No-throw guarantee: this member function never throws
+    exceptions.
 
-  @sa @ref operator[](const json_pointer&) for unchecked access by reference
+    @liveexample{The following code exemplifies `is_object()` for all JSON
+    types.,is_object}
 
-  @since version 2.0.2
-  */
-  template <
-      class ValueType,
-      typename std::enable_if<
-          std::is_convertible<basic_json_t, ValueType>::value, int>::type = 0>
-  ValueType value(const json_pointer &ptr, ValueType default_value) const
-  {
-    // at only works for objects
-    if (is_object())
+    @since version 1.0.0
+    */
+    constexpr bool is_object() const noexcept
     {
-      // 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; }
+        return m_type == value_t::object;
     }
 
-    JSON_THROW(std::domain_error("cannot use value() with " + type_name()));
-  }
-
-  /*!
-  @brief overload for a default value of type const char*
-  @copydoc basic_json::value(const json_pointer&, ValueType) const
-  */
-  string_t value(const json_pointer &ptr, const char *default_value) const
-  {
-    return value(ptr, string_t(default_value));
-  }
-
-  /*!
-  @brief access the first element
+    /*!
+    @brief return whether value is an array
 
-  Returns a reference to the first element in the container. For a JSON
-  container `c`, the expression `c.front()` is equivalent to `*c.begin()`.
+    This function returns true iff the JSON value is an array.
 
-  @return In case of a structured type (array or object), a reference to the
-  first element is returned. In case of number, string, or boolean values, a
-  reference to the value is returned.
+    @return `true` if type is array, `false` otherwise.
 
-  @complexity Constant.
+    @complexity Constant.
 
-  @pre The JSON value must not be `null` (would throw `std::out_of_range`)
-  or an empty array or object (undefined behavior, **guarded by
-  assertions**).
-  @post The JSON value remains unchanged.
+    @exceptionsafety No-throw guarantee: this member function never throws
+    exceptions.
 
-  @throw std::out_of_range when called on `null` value
+    @liveexample{The following code exemplifies `is_array()` for all JSON
+    types.,is_array}
 
-  @liveexample{The following code shows an example for `front()`.,front}
+    @since version 1.0.0
+    */
+    constexpr bool is_array() const noexcept
+    {
+        return m_type == value_t::array;
+    }
 
-  @sa @ref back() -- access the last element
+    /*!
+    @brief return whether value is a string
 
-  @since version 1.0.0
-  */
-  reference front() { return *begin(); }
+    This function returns true iff the JSON value is a string.
 
-  /*!
-  @copydoc basic_json::front()
-  */
-  const_reference front() const { return *cbegin(); }
+    @return `true` if type is string, `false` otherwise.
 
-  /*!
-  @brief access the last element
+    @complexity Constant.
 
-  Returns a reference to the last element in the container. For a JSON
-  container `c`, the expression `c.back()` is equivalent to
-  @code {.cpp}
-  auto tmp = c.end();
-  --tmp;
-  return *tmp;
-  @endcode
+    @exceptionsafety No-throw guarantee: this member function never throws
+    exceptions.
 
-  @return In case of a structured type (array or object), a reference to the
-  last element is returned. In case of number, string, or boolean values, a
-  reference to the value is returned.
+    @liveexample{The following code exemplifies `is_string()` for all JSON
+    types.,is_string}
 
-  @complexity Constant.
+    @since version 1.0.0
+    */
+    constexpr bool is_string() const noexcept
+    {
+        return m_type == value_t::string;
+    }
 
-  @pre The JSON value must not be `null` (would throw `std::out_of_range`)
-  or an empty array or object (undefined behavior, **guarded by
-  assertions**).
-  @post The JSON value remains unchanged.
+    /*!
+    @brief return whether value is discarded
 
-  @throw std::out_of_range when called on `null` value.
+    This function returns true iff the JSON value was discarded during parsing
+    with a callback function (see @ref parser_callback_t).
 
-  @liveexample{The following code shows an example for `back()`.,back}
+    @note This function will always be `false` for JSON values after parsing.
+    That is, discarded values can only occur during parsing, but will be
+    removed when inside a structured value or replaced by null in other cases.
 
-  @sa @ref front() -- access the first element
+    @return `true` if type is discarded, `false` otherwise.
 
-  @since version 1.0.0
-  */
-  reference back()
-  {
-    auto tmp = end();
-    --tmp;
-    return *tmp;
-  }
-
-  /*!
-  @copydoc basic_json::back()
-  */
-  const_reference back() const
-  {
-    auto tmp = cend();
-    --tmp;
-    return *tmp;
-  }
+    @complexity Constant.
 
-  /*!
-  @brief remove element given an iterator
+    @exceptionsafety No-throw guarantee: this member function never throws
+    exceptions.
 
-  Removes the element specified by iterator @a pos. The iterator @a pos must
-  be valid and dereferenceable. Thus the `end()` iterator (which is valid,
-  but is not dereferenceable) cannot be used as a value for @a pos.
+    @liveexample{The following code exemplifies `is_discarded()` for all JSON
+    types.,is_discarded}
 
-  If called on a primitive type other than `null`, the resulting JSON value
-  will be `null`.
+    @since version 1.0.0
+    */
+    constexpr bool is_discarded() const noexcept
+    {
+        return m_type == value_t::discarded;
+    }
 
-  @param[in] pos iterator to the element to remove
-  @return Iterator following the last removed element. If the iterator @a
-  pos refers to the last element, the `end()` iterator is returned.
+    /*!
+    @brief return the type of the JSON value (implicit)
 
-  @tparam IteratorType an @ref iterator or @ref const_iterator
+    Implicitly return the type of the JSON value as a value from the @ref
+    value_t enumeration.
 
-  @post Invalidates iterators and references at or after the point of the
-  erase, including the `end()` iterator.
+    @return the type of the JSON value
 
-  @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
-  iterator (i.e., any iterator which is not `begin()`); example: `"iterator
-  out of range"`
+    @complexity Constant.
 
-  @complexity The complexity depends on the type:
-  - objects: amortized constant
-  - arrays: linear in distance between pos and the end of the container
-  - strings: linear in the length of the string
-  - other types: constant
+    @exceptionsafety No-throw guarantee: this member function never throws
+    exceptions.
 
-  @liveexample{The example shows the result of `erase()` for different JSON
-  types.,erase__IteratorType}
+    @liveexample{The following code exemplifies the @ref value_t operator for
+    all JSON types.,operator__value_t}
 
-  @sa @ref erase(IteratorType, IteratorType) -- removes the elements in
-  the given range
-  @sa @ref erase(const typename object_t::key_type&) -- removes the element
-  from an object at the given key
-  @sa @ref erase(const size_type) -- removes the element from an array at
-  the given index
+    @since version 1.0.0
+    */
+    constexpr operator value_t() const noexcept { return m_type; }
 
-  @since version 1.0.0
-  */
-  template <
-      class IteratorType,
-      typename std::enable_if<
-          std::is_same<IteratorType, typename basic_json_t::iterator>::value or
-              std::is_same<IteratorType,
-                           typename basic_json_t::const_iterator>::value,
-          int>::type = 0>
-  IteratorType erase(IteratorType pos)
-  {
-    // make sure iterator fits the current value
-    if (this != pos.m_object)
-    {
-      JSON_THROW(std::domain_error("iterator does not fit current value"));
-    }
+    /// @}
 
-    IteratorType result = end();
+private:
+    //////////////////
+    // value access //
+    //////////////////
 
-    switch (m_type)
+    /// 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
     {
-      case value_t::boolean:
-      case value_t::number_float:
-      case value_t::number_integer:
-      case value_t::number_unsigned:
-      case value_t::string:
-      {
-        if (not pos.m_it.primitive_iterator.is_begin())
+        if (is_object())
         {
-          JSON_THROW(std::out_of_range("iterator out of range"));
+            return T(m_value.object->begin(), m_value.object->end());
         }
 
-        if (is_string())
+        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())
         {
-          AllocatorType<string_t> alloc;
-          alloc.destroy(m_value.string);
-          alloc.deallocate(m_value.string, 1);
-          m_value.string = nullptr;
+            return *(m_value.object);
         }
 
-        m_type = value_t::null;
-        assert_invariant();
-        break;
-      }
-
-      case value_t::object:
-      {
-        result.m_it.object_iterator =
-            m_value.object->erase(pos.m_it.object_iterator);
-        break;
-      }
-
-      case value_t::array:
-      {
-        result.m_it.array_iterator =
-            m_value.array->erase(pos.m_it.array_iterator);
-        break;
-      }
-
-      default:
-      {
-        JSON_THROW(std::domain_error("cannot use erase() with " + type_name()));
-      }
+        JSON_THROW(
+            std::domain_error("type must be object, but is " + type_name()));
     }
 
-    return result;
-  }
-
-  /*!
-  @brief remove elements given an iterator range
-
-  Removes the element specified by the range `[first; last)`. The iterator
-  @a first does not need to be dereferenceable if `first == last`: erasing
-  an empty range is a no-op.
-
-  If called on a primitive type other than `null`, the resulting JSON value
-  will be `null`.
-
-  @param[in] first iterator to the beginning of the range to remove
-  @param[in] last iterator past the end of the range to remove
-  @return Iterator following the last removed element. If the iterator @a
-  second refers to the last element, the `end()` iterator is returned.
-
-  @tparam IteratorType an @ref iterator or @ref const_iterator
-
-  @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
-  iterators (i.e., if `first != begin()` and `last != end()`); example:
-  `"iterators out of range"`
+    /// 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;
+        }
 
-  @complexity The complexity depends on the type:
-  - objects: `log(size()) + std::distance(first, last)`
-  - arrays: linear in the distance between @a first and @a last, plus linear
-    in the distance between @a last and end of the container
-  - strings: linear in the length of the string
-  - other types: constant
+        JSON_THROW(
+            std::domain_error("type must be array, but is " + type_name()));
+    }
 
-  @liveexample{The example shows the result of `erase()` for different JSON
-  types.,erase__IteratorType_IteratorType}
+    /// 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;
+        }
 
-  @sa @ref erase(IteratorType) -- removes the element at a given position
-  @sa @ref erase(const typename object_t::key_type&) -- removes the element
-  from an object at the given key
-  @sa @ref erase(const size_type) -- removes the element from an array at
-  the given index
+        JSON_THROW(
+            std::domain_error("type must be array, but is " + type_name()));
+    }
 
-  @since version 1.0.0
-  */
-  template <
-      class IteratorType,
-      typename std::enable_if<
-          std::is_same<IteratorType, typename basic_json_t::iterator>::value or
-              std::is_same<IteratorType,
-                           typename basic_json_t::const_iterator>::value,
-          int>::type = 0>
-  IteratorType erase(IteratorType first, IteratorType last)
-  {
-    // make sure iterator fits the current value
-    if (this != first.m_object or this != last.m_object)
+    /// 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
     {
-      JSON_THROW(std::domain_error("iterators do not fit current value"));
-    }
+        if (is_array())
+        {
+            return T(m_value.array->begin(), m_value.array->end());
+        }
 
-    IteratorType result = end();
+        JSON_THROW(
+            std::domain_error("type must be array, but is " + type_name()));
+    }
 
-    switch (m_type)
+    /// get an array (explicit)
+    array_t get_impl(array_t * /*unused*/) const
     {
-      case value_t::boolean:
-      case value_t::number_float:
-      case value_t::number_integer:
-      case value_t::number_unsigned:
-      case value_t::string:
-      {
-        if (not first.m_it.primitive_iterator.is_begin() or
-            not last.m_it.primitive_iterator.is_end())
+        if (is_array())
         {
-          JSON_THROW(std::out_of_range("iterators out of range"));
+            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())
         {
-          AllocatorType<string_t> alloc;
-          alloc.destroy(m_value.string);
-          alloc.deallocate(m_value.string, 1);
-          m_value.string = nullptr;
+            return *m_value.string;
         }
 
-        m_type = value_t::null;
-        assert_invariant();
-        break;
-      }
-
-      case value_t::object:
-      {
-        result.m_it.object_iterator = m_value.object->erase(
-            first.m_it.object_iterator, last.m_it.object_iterator);
-        break;
-      }
-
-      case value_t::array:
-      {
-        result.m_it.array_iterator = m_value.array->erase(
-            first.m_it.array_iterator, last.m_it.array_iterator);
-        break;
-      }
-
-      default:
-      {
-        JSON_THROW(std::domain_error("cannot use erase() with " + type_name()));
-      }
+        JSON_THROW(
+            std::domain_error("type must be string, but is " + type_name()));
     }
 
-    return result;
-  }
-
-  /*!
-  @brief remove element from a JSON object given a key
-
-  Removes elements from a JSON object with the key value @a key.
-
-  @param[in] key value of the elements to remove
+    /// 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);
+        }
 
-  @return Number of elements removed. If @a ObjectType is the default
-  `std::map` type, the return value will always be `0` (@a key was not
-  found) or `1` (@a key was found).
+        case value_t::number_unsigned:
+        {
+            return static_cast<T>(m_value.number_unsigned);
+        }
 
-  @post References and iterators to the erased elements are invalidated.
-  Other references and iterators are not affected.
+        case value_t::number_float:
+        {
+            return static_cast<T>(m_value.number_float);
+        }
 
-  @throw std::domain_error when called on a type other than JSON object;
-  example: `"cannot use erase() with null"`
+        default:
+        {
+            JSON_THROW(std::domain_error("type must be number, but is " +
+                                         type_name()));
+        }
+        }
+    }
 
-  @complexity `log(size()) + count(key)`
+    /// get a boolean (explicit)
+    boolean_t get_impl(boolean_t * /*unused*/) const
+    {
+        if (is_boolean())
+        {
+            return m_value.boolean;
+        }
+        else
+        {
+            JSON_THROW(std::domain_error("type must be boolean, but is " +
+                                         type_name()));
+        }
+    }
 
-  @liveexample{The example shows the effect of `erase()`.,erase__key_type}
+    /// get a pointer to the value (object)
+    object_t *get_impl_ptr(object_t * /*unused*/) noexcept
+    {
+        return is_object() ? m_value.object : nullptr;
+    }
 
-  @sa @ref erase(IteratorType) -- removes the element at a given position
-  @sa @ref erase(IteratorType, IteratorType) -- removes the elements in
-  the given range
-  @sa @ref erase(const size_type) -- removes the element from an array at
-  the given index
+    /// get a pointer to the value (object)
+    constexpr const object_t *get_impl_ptr(const object_t * /*unused*/) const
+        noexcept
+    {
+        return is_object() ? m_value.object : nullptr;
+    }
 
-  @since version 1.0.0
-  */
-  size_type erase(const typename object_t::key_type &key)
-  {
-    // this erase only works for objects
-    if (is_object())
+    /// get a pointer to the value (array)
+    array_t *get_impl_ptr(array_t * /*unused*/) noexcept
     {
-      return m_value.object->erase(key);
+        return is_array() ? m_value.array : nullptr;
     }
 
-    JSON_THROW(std::domain_error("cannot use erase() with " + type_name()));
-  }
+    /// get a pointer to the value (array)
+    constexpr const array_t *get_impl_ptr(const array_t * /*unused*/) const
+        noexcept
+    {
+        return is_array() ? m_value.array : nullptr;
+    }
 
-  /*!
-  @brief remove element from a JSON array given an index
+    /// get a pointer to the value (string)
+    string_t *get_impl_ptr(string_t * /*unused*/) noexcept
+    {
+        return is_string() ? m_value.string : nullptr;
+    }
 
-  Removes element from a JSON array at the index @a idx.
+    /// get a pointer to the value (string)
+    constexpr const string_t *get_impl_ptr(const string_t * /*unused*/) const
+        noexcept
+    {
+        return is_string() ? m_value.string : nullptr;
+    }
 
-  @param[in] idx index of the element to remove
+    /// get a pointer to the value (boolean)
+    boolean_t *get_impl_ptr(boolean_t * /*unused*/) noexcept
+    {
+        return is_boolean() ? &m_value.boolean : nullptr;
+    }
 
-  @throw std::domain_error when called on a type other than JSON array;
-  example: `"cannot use erase() with null"`
-  @throw std::out_of_range when `idx >= size()`; example: `"array index 17
-  is out of range"`
+    /// get a pointer to the value (boolean)
+    constexpr const boolean_t *get_impl_ptr(const boolean_t * /*unused*/) const
+        noexcept
+    {
+        return is_boolean() ? &m_value.boolean : nullptr;
+    }
 
-  @complexity Linear in distance between @a idx and the end of the container.
+    /// get a pointer to the value (integer number)
+    number_integer_t *get_impl_ptr(number_integer_t * /*unused*/) noexcept
+    {
+        return is_number_integer() ? &m_value.number_integer : nullptr;
+    }
 
-  @liveexample{The example shows the effect of `erase()`.,erase__size_type}
+    /// get a pointer to the value (integer number)
+    constexpr const number_integer_t *
+    get_impl_ptr(const number_integer_t * /*unused*/) const noexcept
+    {
+        return is_number_integer() ? &m_value.number_integer : nullptr;
+    }
 
-  @sa @ref erase(IteratorType) -- removes the element at a given position
-  @sa @ref erase(IteratorType, IteratorType) -- removes the elements in
-  the given range
-  @sa @ref erase(const typename object_t::key_type&) -- removes the element
-  from an object at the given key
+    /// get a pointer to the value (unsigned number)
+    number_unsigned_t *get_impl_ptr(number_unsigned_t * /*unused*/) noexcept
+    {
+        return is_number_unsigned() ? &m_value.number_unsigned : nullptr;
+    }
 
-  @since version 1.0.0
-  */
-  void erase(const size_type idx)
-  {
-    // this erase only works for arrays
-    if (is_array())
+    /// get a pointer to the value (unsigned number)
+    constexpr const number_unsigned_t *
+    get_impl_ptr(const number_unsigned_t * /*unused*/) const noexcept
     {
-      if (idx >= size())
-      {
-        JSON_THROW(std::out_of_range("array index " + std::to_string(idx) +
-                                     " is out of range"));
-      }
+        return is_number_unsigned() ? &m_value.number_unsigned : nullptr;
+    }
 
-      m_value.array->erase(m_value.array->begin() +
-                           static_cast<difference_type>(idx));
+    /// get a pointer to the value (floating-point number)
+    number_float_t *get_impl_ptr(number_float_t * /*unused*/) noexcept
+    {
+        return is_number_float() ? &m_value.number_float : nullptr;
     }
-    else
+
+    /// get a pointer to the value (floating-point number)
+    constexpr const number_float_t *
+    get_impl_ptr(const number_float_t * /*unused*/) const noexcept
     {
-      JSON_THROW(std::domain_error("cannot use erase() with " + type_name()));
+        return is_number_float() ? &m_value.number_float : nullptr;
     }
-  }
 
-  /// @}
+    /*!
+    @brief helper function to implement get_ref()
 
-  ////////////
-  // lookup //
-  ////////////
+    This funcion helps to implement get_ref() without code duplication for
+    const and non-const overloads
 
-  /// @name lookup
-  /// @{
+    @tparam ThisType will be deduced as `basic_json` or `const basic_json`
 
-  /*!
-  @brief find an element in a JSON object
+    @throw std::domain_error if ReferenceType does not match underlying value
+    type of the current JSON
+    */
+    template <typename ReferenceType, typename ThisType>
+    static ReferenceType get_ref_impl(ThisType &obj)
+    {
+        // helper type
+        using PointerType = typename std::add_pointer<ReferenceType>::type;
 
-  Finds an element in a JSON object with key equivalent to @a key. If the
-  element is not found or the JSON value is not an object, end() is
-  returned.
+        // delegate the call to get_ptr<>()
+        auto ptr = obj.template get_ptr<PointerType>();
 
-  @note This method always returns @ref end() when executed on a JSON type
-        that is not an object.
+        if (ptr != nullptr)
+        {
+            return *ptr;
+        }
 
-  @param[in] key key value of the element to search for
+        throw std::domain_error(
+            "incompatible ReferenceType for get_ref, actual type is " +
+            obj.type_name());
+    }
 
-  @return Iterator to an element with key equivalent to @a key. If no such
-  element is found or the JSON value is not an object, past-the-end (see
-  @ref end()) iterator is returned.
+public:
+    /// @name value access
+    /// Direct access to the stored value of a JSON value.
+    /// @{
 
-  @complexity Logarithmic in the size of the JSON object.
+    /*!
+    @brief get a value (explicit)
 
-  @liveexample{The example shows how `find()` is used.,find__key_type}
+    Explicit type conversion between the JSON value and a compatible value.
 
-  @since version 1.0.0
-  */
-  iterator find(typename object_t::key_type key)
-  {
-    auto result = end();
+    @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
 
-    if (is_object())
-    {
-      result.m_it.object_iterator = m_value.object->find(key);
-    }
+    @return copy of the JSON value, converted to type @a ValueType
 
-    return result;
-  }
+    @throw std::domain_error in case passed type @a ValueType is incompatible
+    to JSON; example: `"type must be object, but is null"`
 
-  /*!
-  @brief find an element in a JSON object
-  @copydoc find(typename object_t::key_type)
-  */
-  const_iterator find(typename object_t::key_type key) const
-  {
-    auto result = cend();
+    @complexity Linear in the size of the JSON value.
 
-    if (is_object())
-    {
-      result.m_it.object_iterator = m_value.object->find(key);
-    }
+    @liveexample{The example below shows several conversions from JSON values
+    to other types. There a few things to note: (1) Floating-point numbers can
+    be converted to integers\, (2) A JSON array can be converted to a standard
+    `std::vector<short>`\, (3) A JSON object can be converted to C++
+    associative containers such as `std::unordered_map<std::string\,
+    json>`.,get__ValueType_const}
 
-    return result;
-  }
+    @internal
+    The idea of using a casted null pointer to choose the correct
+    implementation is from <http://stackoverflow.com/a/8315197/266378>.
+    @endinternal
 
-  /*!
-  @brief returns the number of occurrences of a key in a JSON object
+    @sa @ref operator ValueType() const for implicit conversion
+    @sa @ref get() for pointer-member access
 
-  Returns the number of elements with key @a key. If ObjectType is the
-  default `std::map` type, the return value will always be `0` (@a key was
-  not found) or `1` (@a key was found).
+    @since version 1.0.0
+    */
+    template <typename ValueType,
+              typename std::enable_if<not std::is_pointer<ValueType>::value,
+                                      int>::type = 0>
+    ValueType get() const
+    {
+        return get_impl(static_cast<ValueType *>(nullptr));
+    }
 
-  @note This method always returns `0` when executed on a JSON type that is
-        not an object.
+    /*!
+    @brief get a pointer value (explicit)
 
-  @param[in] key key value of the element to count
+    Explicit pointer access to the internally stored JSON value. No copies are
+    made.
 
-  @return Number of elements with key @a key. If the JSON value is not an
-  object, the return value will be `0`.
+    @warning The pointer becomes invalid if the underlying JSON object
+    changes.
 
-  @complexity Logarithmic in the size of the JSON object.
+    @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref
+    object_t, @ref string_t, @ref boolean_t, @ref number_integer_t,
+    @ref number_unsigned_t, or @ref number_float_t.
 
-  @liveexample{The example shows how `count()` is used.,count}
+    @return pointer to the internally stored JSON value if the requested
+    pointer type @a PointerType fits to the JSON value; `nullptr` otherwise
 
-  @since version 1.0.0
-  */
-  size_type count(typename object_t::key_type key) const
-  {
-    // return 0 for all nonobject types
-    return is_object() ? m_value.object->count(key) : 0;
-  }
+    @complexity Constant.
 
-  /// @}
+    @liveexample{The example below shows how pointers to internal values of a
+    JSON value can be requested. Note that no type conversions are made and a
+    `nullptr` is returned if the value and the requested pointer type does not
+    match.,get__PointerType}
 
-  ///////////////
-  // iterators //
-  ///////////////
+    @sa @ref get_ptr() for explicit pointer-member access
 
-  /// @name iterators
-  /// @{
+    @since version 1.0.0
+    */
+    template <typename PointerType,
+              typename std::enable_if<std::is_pointer<PointerType>::value,
+                                      int>::type = 0>
+    PointerType get() noexcept
+    {
+        // delegate the call to get_ptr
+        return get_ptr<PointerType>();
+    }
 
-  /*!
-  @brief returns an iterator to the first element
+    /*!
+    @brief get a pointer value (explicit)
+    @copydoc get()
+    */
+    template <typename PointerType,
+              typename std::enable_if<std::is_pointer<PointerType>::value,
+                                      int>::type = 0>
+    constexpr const PointerType get() const noexcept
+    {
+        // delegate the call to get_ptr
+        return get_ptr<PointerType>();
+    }
 
-  Returns an iterator to the first element.
+    /*!
+    @brief get a pointer value (implicit)
 
-  @image html range-begin-end.svg "Illustration from cppreference.com"
+    Implicit pointer access to the internally stored JSON value. No copies are
+    made.
 
-  @return iterator to the first element
+    @warning Writing data to the pointee of the result yields an undefined
+    state.
 
-  @complexity Constant.
+    @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref
+    object_t, @ref string_t, @ref boolean_t, @ref number_integer_t,
+    @ref number_unsigned_t, or @ref number_float_t. Enforced by a static
+    assertion.
 
-  @requirement This function helps `basic_json` satisfying the
-  [Container](http://en.cppreference.com/w/cpp/concept/Container)
-  requirements:
-  - The complexity is constant.
+    @return pointer to the internally stored JSON value if the requested
+    pointer type @a PointerType fits to the JSON value; `nullptr` otherwise
 
-  @liveexample{The following code shows an example for `begin()`.,begin}
+    @complexity Constant.
 
-  @sa @ref cbegin() -- returns a const iterator to the beginning
-  @sa @ref end() -- returns an iterator to the end
-  @sa @ref cend() -- returns a const iterator to the end
+    @liveexample{The example below shows how pointers to internal values of a
+    JSON value can be requested. Note that no type conversions are made and a
+    `nullptr` is returned if the value and the requested pointer type does not
+    match.,get_ptr}
 
-  @since version 1.0.0
-  */
-  iterator begin() noexcept
-  {
-    iterator result(this);
-    result.set_begin();
-    return result;
-  }
+    @since version 1.0.0
+    */
+    template <typename PointerType,
+              typename std::enable_if<std::is_pointer<PointerType>::value,
+                                      int>::type = 0>
+    PointerType get_ptr() noexcept
+    {
+        // get the type of the PointerType (remove pointer and const)
+        using pointee_t =
+            typename std::remove_const<typename std::remove_pointer<
+                typename std::remove_const<PointerType>::type>::type>::type;
+        // make sure the type matches the allowed types
+        static_assert(std::is_same<object_t, pointee_t>::value or
+                          std::is_same<array_t, pointee_t>::value or
+                          std::is_same<string_t, pointee_t>::value or
+                          std::is_same<boolean_t, pointee_t>::value or
+                          std::is_same<number_integer_t, pointee_t>::value or
+                          std::is_same<number_unsigned_t, pointee_t>::value or
+                          std::is_same<number_float_t, pointee_t>::value,
+                      "incompatible pointer type");
+
+        // delegate the call to get_impl_ptr<>()
+        return get_impl_ptr(static_cast<PointerType>(nullptr));
+    }
 
-  /*!
-  @copydoc basic_json::cbegin()
-  */
-  const_iterator begin() const noexcept { return cbegin(); }
+    /*!
+    @brief get a pointer value (implicit)
+    @copydoc get_ptr()
+    */
+    template <
+        typename PointerType,
+        typename std::enable_if<std::is_pointer<PointerType>::value and
+                                    std::is_const<typename std::remove_pointer<
+                                        PointerType>::type>::value,
+                                int>::type = 0>
+    constexpr const PointerType get_ptr() const noexcept
+    {
+        // get the type of the PointerType (remove pointer and const)
+        using pointee_t =
+            typename std::remove_const<typename std::remove_pointer<
+                typename std::remove_const<PointerType>::type>::type>::type;
+        // make sure the type matches the allowed types
+        static_assert(std::is_same<object_t, pointee_t>::value or
+                          std::is_same<array_t, pointee_t>::value or
+                          std::is_same<string_t, pointee_t>::value or
+                          std::is_same<boolean_t, pointee_t>::value or
+                          std::is_same<number_integer_t, pointee_t>::value or
+                          std::is_same<number_unsigned_t, pointee_t>::value or
+                          std::is_same<number_float_t, pointee_t>::value,
+                      "incompatible pointer type");
+
+        // delegate the call to get_impl_ptr<>() const
+        return get_impl_ptr(static_cast<const PointerType>(nullptr));
+    }
 
-  /*!
-  @brief returns a const iterator to the first element
+    /*!
+    @brief get a reference value (implicit)
 
-  Returns a const iterator to the first element.
+    Implict reference access to the internally stored JSON value. No copies
+    are made.
 
-  @image html range-begin-end.svg "Illustration from cppreference.com"
+    @warning Writing data to the referee of the result yields an undefined
+    state.
 
-  @return const iterator to the first element
+    @tparam ReferenceType reference type; must be a reference to @ref array_t,
+    @ref object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, or
+    @ref number_float_t. Enforced by static assertion.
 
-  @complexity Constant.
+    @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
 
-  @requirement This function helps `basic_json` satisfying the
-  [Container](http://en.cppreference.com/w/cpp/concept/Container)
-  requirements:
-  - The complexity is constant.
-  - Has the semantics of `const_cast<const basic_json&>(*this).begin()`.
+    @throw std::domain_error in case passed type @a ReferenceType is
+    incompatible with the stored JSON value
 
-  @liveexample{The following code shows an example for `cbegin()`.,cbegin}
+    @complexity Constant.
 
-  @sa @ref begin() -- returns an iterator to the beginning
-  @sa @ref end() -- returns an iterator to the end
-  @sa @ref cend() -- returns a const iterator to the end
+    @liveexample{The example shows several calls to `get_ref()`.,get_ref}
 
-  @since version 1.0.0
-  */
-  const_iterator cbegin() const noexcept
-  {
-    const_iterator result(this);
-    result.set_begin();
-    return result;
-  }
+    @since version 1.1.0
+    */
+    template <typename ReferenceType,
+              typename std::enable_if<std::is_reference<ReferenceType>::value,
+                                      int>::type = 0>
+    ReferenceType get_ref()
+    {
+        // delegate call to get_ref_impl
+        return get_ref_impl<ReferenceType>(*this);
+    }
 
-  /*!
-  @brief returns an iterator to one past the last element
+    /*!
+    @brief get a reference value (implicit)
+    @copydoc get_ref()
+    */
+    template <typename ReferenceType,
+              typename std::enable_if<
+                  std::is_reference<ReferenceType>::value and
+                      std::is_const<typename std::remove_reference<
+                          ReferenceType>::type>::value,
+                  int>::type = 0>
+    ReferenceType get_ref() const
+    {
+        // delegate call to get_ref_impl
+        return get_ref_impl<ReferenceType>(*this);
+    }
 
-  Returns an iterator to one past the last element.
+    /*!
+    @brief get a value (implicit)
 
-  @image html range-begin-end.svg "Illustration from cppreference.com"
+    Implicit type conversion between the JSON value and a compatible value.
+    The call is realized by calling @ref get() const.
 
-  @return iterator one past the last element
+    @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 character type of @ref string_t
+    as well as an initializer list of this type is excluded to avoid
+    ambiguities as these types implicitly convert to `std::string`.
 
-  @complexity Constant.
+    @return copy of the JSON value, converted to type @a ValueType
 
-  @requirement This function helps `basic_json` satisfying the
-  [Container](http://en.cppreference.com/w/cpp/concept/Container)
-  requirements:
-  - The complexity is constant.
+    @throw std::domain_error in case passed type @a ValueType is incompatible
+    to JSON, thrown by @ref get() const
 
-  @liveexample{The following code shows an example for `end()`.,end}
+    @complexity Linear in the size of the JSON value.
 
-  @sa @ref cend() -- returns a const iterator to the end
-  @sa @ref begin() -- returns an iterator to the beginning
-  @sa @ref cbegin() -- returns a const iterator to the beginning
+    @liveexample{The example below shows several conversions from JSON values
+    to other types. There a few things to note: (1) Floating-point numbers can
+    be converted to integers\, (2) A JSON array can be converted to a standard
+    `std::vector<short>`\, (3) A JSON object can be converted to C++
+    associative containers such as `std::unordered_map<std::string\,
+    json>`.,operator__ValueType}
 
-  @since version 1.0.0
-  */
-  iterator end() noexcept
-  {
-    iterator result(this);
-    result.set_end();
-    return result;
-  }
+    @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
+#endif
+                  ,
+                  int>::type = 0>
+    operator ValueType() const
+    {
+        // delegate the call to get<>() const
+        return get<ValueType>();
+    }
 
-  /*!
-  @copydoc basic_json::cend()
-  */
-  const_iterator end() const noexcept { return cend(); }
+    /// @}
 
-  /*!
-  @brief returns a const iterator to one past the last element
+    ////////////////////
+    // element access //
+    ////////////////////
 
-  Returns a const iterator to one past the last element.
+    /// @name element access
+    /// Access to the JSON value.
+    /// @{
 
-  @image html range-begin-end.svg "Illustration from cppreference.com"
+    /*!
+    @brief access specified array element with bounds checking
 
-  @return const iterator one past the last element
+    Returns a reference to the element at specified location @a idx, with
+    bounds checking.
 
-  @complexity Constant.
+    @param[in] idx  index of the element to access
 
-  @requirement This function helps `basic_json` satisfying the
-  [Container](http://en.cppreference.com/w/cpp/concept/Container)
-  requirements:
-  - The complexity is constant.
-  - Has the semantics of `const_cast<const basic_json&>(*this).end()`.
+    @return reference to the element at index @a idx
 
-  @liveexample{The following code shows an example for `cend()`.,cend}
+    @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"`
 
-  @sa @ref end() -- returns an iterator to the end
-  @sa @ref begin() -- returns an iterator to the beginning
-  @sa @ref cbegin() -- returns a const iterator to the beginning
+    @complexity Constant.
 
-  @since version 1.0.0
-  */
-  const_iterator cend() const noexcept
-  {
-    const_iterator result(this);
-    result.set_end();
-    return result;
-  }
+    @liveexample{The example below shows how array elements can be read and
+    written using `at()`.,at__size_type}
 
-  /*!
-  @brief returns an iterator to the reverse-beginning
+    @since version 1.0.0
+    */
+    reference at(size_type idx)
+    {
+        // at only works for arrays
+        if (is_array())
+        {
+            JSON_TRY { return m_value.array->at(idx); }
+            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"));
+            }
+        }
+        else
+        {
+            JSON_THROW(
+                std::domain_error("cannot use at() with " + type_name()));
+        }
+    }
 
-  Returns an iterator to the reverse-beginning; that is, the last element.
+    /*!
+    @brief access specified array element with bounds checking
 
-  @image html range-rbegin-rend.svg "Illustration from cppreference.com"
+    Returns a const reference to the element at specified location @a idx,
+    with bounds checking.
 
-  @complexity Constant.
+    @param[in] idx  index of the element to access
 
-  @requirement This function helps `basic_json` satisfying the
-  [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer)
-  requirements:
-  - The complexity is constant.
-  - Has the semantics of `reverse_iterator(end())`.
+    @return const reference to the element at index @a idx
 
-  @liveexample{The following code shows an example for `rbegin()`.,rbegin}
+    @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"`
 
-  @sa @ref crbegin() -- returns a const reverse iterator to the beginning
-  @sa @ref rend() -- returns a reverse iterator to the end
-  @sa @ref crend() -- returns a const reverse iterator to the end
+    @complexity Constant.
 
-  @since version 1.0.0
-  */
-  reverse_iterator rbegin() noexcept { return reverse_iterator(end()); }
+    @liveexample{The example below shows how array elements can be read using
+    `at()`.,at__size_type_const}
 
-  /*!
-  @copydoc basic_json::crbegin()
-  */
-  const_reverse_iterator rbegin() const noexcept { return crbegin(); }
+    @since version 1.0.0
+    */
+    const_reference at(size_type idx) const
+    {
+        // at only works for arrays
+        if (is_array())
+        {
+            JSON_TRY { return m_value.array->at(idx); }
+            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"));
+            }
+        }
+        else
+        {
+            JSON_THROW(
+                std::domain_error("cannot use at() with " + type_name()));
+        }
+    }
 
-  /*!
-  @brief returns an iterator to the reverse-end
+    /*!
+    @brief access specified object element with bounds checking
 
-  Returns an iterator to the reverse-end; that is, one before the first
-  element.
+    Returns a reference to the element at with specified key @a key, with
+    bounds checking.
 
-  @image html range-rbegin-rend.svg "Illustration from cppreference.com"
+    @param[in] key  key of the element to access
 
-  @complexity Constant.
+    @return reference to the element at key @a key
 
-  @requirement This function helps `basic_json` satisfying the
-  [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer)
-  requirements:
-  - The complexity is constant.
-  - Has the semantics of `reverse_iterator(begin())`.
+    @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"`
 
-  @liveexample{The following code shows an example for `rend()`.,rend}
+    @complexity Logarithmic in the size of the container.
 
-  @sa @ref crend() -- returns a const reverse iterator to the end
-  @sa @ref rbegin() -- returns a reverse iterator to the beginning
-  @sa @ref crbegin() -- returns a const reverse iterator to the beginning
+    @liveexample{The example below shows how object elements can be read and
+    written using `at()`.,at__object_t_key_type}
 
-  @since version 1.0.0
-  */
-  reverse_iterator rend() noexcept { return reverse_iterator(begin()); }
+    @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
 
-  /*!
-  @copydoc basic_json::crend()
-  */
-  const_reverse_iterator rend() const noexcept { return crend(); }
+    @since version 1.0.0
+    */
+    reference at(const typename object_t::key_type &key)
+    {
+        // at only works for objects
+        if (is_object())
+        {
+            JSON_TRY { return m_value.object->at(key); }
+            JSON_CATCH(std::out_of_range &)
+            {
+                // create better exception explanation
+                JSON_THROW(std::out_of_range("key '" + key + "' not found"));
+            }
+        }
+        else
+        {
+            JSON_THROW(
+                std::domain_error("cannot use at() with " + type_name()));
+        }
+    }
 
-  /*!
-  @brief returns a const reverse iterator to the last element
+    /*!
+    @brief access specified object element with bounds checking
 
-  Returns a const iterator to the reverse-beginning; that is, the last
-  element.
+    Returns a const reference to the element at with specified key @a key,
+    with bounds checking.
 
-  @image html range-rbegin-rend.svg "Illustration from cppreference.com"
+    @param[in] key  key of the element to access
 
-  @complexity Constant.
+    @return const reference to the element at key @a key
 
-  @requirement This function helps `basic_json` satisfying the
-  [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer)
-  requirements:
-  - The complexity is constant.
-  - Has the semantics of `const_cast<const basic_json&>(*this).rbegin()`.
+    @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"`
 
-  @liveexample{The following code shows an example for `crbegin()`.,crbegin}
+    @complexity Logarithmic in the size of the container.
 
-  @sa @ref rbegin() -- returns a reverse iterator to the beginning
-  @sa @ref rend() -- returns a reverse iterator to the end
-  @sa @ref crend() -- returns a const reverse iterator to the end
+    @liveexample{The example below shows how object elements can be read using
+    `at()`.,at__object_t_key_type_const}
 
-  @since version 1.0.0
-  */
-  const_reverse_iterator crbegin() const noexcept
-  {
-    return const_reverse_iterator(cend());
-  }
+    @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
 
-  /*!
-  @brief returns a const reverse iterator to one before the first
+    @since version 1.0.0
+    */
+    const_reference at(const typename object_t::key_type &key) const
+    {
+        // at only works for objects
+        if (is_object())
+        {
+            JSON_TRY { return m_value.object->at(key); }
+            JSON_CATCH(std::out_of_range &)
+            {
+                // create better exception explanation
+                JSON_THROW(std::out_of_range("key '" + key + "' not found"));
+            }
+        }
+        else
+        {
+            JSON_THROW(
+                std::domain_error("cannot use at() with " + type_name()));
+        }
+    }
 
-  Returns a const reverse iterator to the reverse-end; that is, one before
-  the first element.
+    /*!
+    @brief access specified array element
 
-  @image html range-rbegin-rend.svg "Illustration from cppreference.com"
+    Returns a reference to the element at specified location @a idx.
 
-  @complexity Constant.
+    @note If @a idx is beyond the range of the array (i.e., `idx >= size()`),
+    then the array is silently filled up with `null` values to make `idx` a
+    valid reference to the last stored element.
 
-  @requirement This function helps `basic_json` satisfying the
-  [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer)
-  requirements:
-  - The complexity is constant.
-  - Has the semantics of `const_cast<const basic_json&>(*this).rend()`.
+    @param[in] idx  index of the element to access
 
-  @liveexample{The following code shows an example for `crend()`.,crend}
+    @return reference to the element at index @a idx
 
-  @sa @ref rend() -- returns a reverse iterator to the end
-  @sa @ref rbegin() -- returns a reverse iterator to the beginning
-  @sa @ref crbegin() -- returns a const reverse iterator to the beginning
+    @throw std::domain_error if JSON is not an array or null; example:
+    `"cannot use operator[] with string"`
 
-  @since version 1.0.0
-  */
-  const_reverse_iterator crend() const noexcept
-  {
-    return const_reverse_iterator(cbegin());
-  }
+    @complexity Constant if @a idx is in the range of the array. Otherwise
+    linear in `idx - size()`.
 
-private:
-  // forward declaration
-  template <typename IteratorType> class iteration_proxy;
+    @liveexample{The example below shows how array elements can be read and
+    written using `[]` operator. Note the addition of `null`
+    values.,operatorarray__size_type}
 
-public:
-  /*!
-  @brief wrapper to access iterator member functions in range-based for
-
-  This function allows to access @ref iterator::key() and @ref
-  iterator::value() during range-based for loops. In these loops, a
-  reference to the JSON values is returned, so there is no access to the
-  underlying iterator.
-
-  @note The name of this function is not yet final and may change in the
-  future.
-  */
-  static iteration_proxy<iterator> iterator_wrapper(reference cont)
-  {
-    return iteration_proxy<iterator>(cont);
-  }
-
-  /*!
-  @copydoc iterator_wrapper(reference)
-  */
-  static iteration_proxy<const_iterator> iterator_wrapper(const_reference cont)
-  {
-    return iteration_proxy<const_iterator>(cont);
-  }
-
-  /// @}
-
-  //////////////
-  // capacity //
-  //////////////
-
-  /// @name capacity
-  /// @{
-
-  /*!
-  @brief checks whether the container is empty
-
-  Checks if a JSON value has no elements.
-
-  @return The return value depends on the different types and is
-          defined as follows:
-          Value type  | return value
-          ----------- | -------------
-          null        | `true`
-          boolean     | `false`
-          string      | `false`
-          number      | `false`
-          object      | result of function `object_t::empty()`
-          array       | result of function `array_t::empty()`
-
-  @note This function does not return whether a string stored as JSON value
-  is empty - it returns whether the JSON container itself is empty which is
-  false in the case of a string.
-
-  @complexity Constant, as long as @ref array_t and @ref object_t satisfy
-  the Container concept; that is, their `empty()` functions have constant
-  complexity.
-
-  @requirement This function helps `basic_json` satisfying the
-  [Container](http://en.cppreference.com/w/cpp/concept/Container)
-  requirements:
-  - The complexity is constant.
-  - Has the semantics of `begin() == end()`.
-
-  @liveexample{The following code uses `empty()` to check if a JSON
-  object contains any elements.,empty}
-
-  @sa @ref size() -- returns the number of elements
-
-  @since version 1.0.0
-  */
-  bool empty() const noexcept
-  {
-    switch (m_type)
-    {
-      case value_t::null:
-      {
-        // null values are empty
-        return true;
-      }
-
-      case value_t::array:
-      {
-        // delegate call to array_t::empty()
-        return m_value.array->empty();
-      }
-
-      case value_t::object:
-      {
-        // delegate call to object_t::empty()
-        return m_value.object->empty();
-      }
-
-      default:
-      {
-        // all other types are nonempty
-        return false;
-      }
-    }
-  }
-
-  /*!
-  @brief returns the number of elements
-
-  Returns the number of elements in a JSON value.
-
-  @return The return value depends on the different types and is
-          defined as follows:
-          Value type  | return value
-          ----------- | -------------
-          null        | `0`
-          boolean     | `1`
-          string      | `1`
-          number      | `1`
-          object      | result of function object_t::size()
-          array       | result of function array_t::size()
-
-  @note This function does not return the length of a string stored as JSON
-  value - it returns the number of elements in the JSON value which is 1 in
-  the case of a string.
-
-  @complexity Constant, as long as @ref array_t and @ref object_t satisfy
-  the Container concept; that is, their size() functions have constant
-  complexity.
-
-  @requirement This function helps `basic_json` satisfying the
-  [Container](http://en.cppreference.com/w/cpp/concept/Container)
-  requirements:
-  - The complexity is constant.
-  - Has the semantics of `std::distance(begin(), end())`.
-
-  @liveexample{The following code calls `size()` on the different value
-  types.,size}
-
-  @sa @ref empty() -- checks whether the container is empty
-  @sa @ref max_size() -- returns the maximal number of elements
-
-  @since version 1.0.0
-  */
-  size_type size() const noexcept
-  {
-    switch (m_type)
-    {
-      case value_t::null:
-      {
-        // null values are empty
-        return 0;
-      }
-
-      case value_t::array:
-      {
-        // delegate call to array_t::size()
-        return m_value.array->size();
-      }
-
-      case value_t::object:
-      {
-        // delegate call to object_t::size()
-        return m_value.object->size();
-      }
-
-      default:
-      {
-        // all other types have size 1
-        return 1;
-      }
-    }
-  }
-
-  /*!
-  @brief returns the maximum possible number of elements
-
-  Returns the maximum number of elements a JSON value is able to hold due to
-  system or library implementation limitations, i.e. `std::distance(begin(),
-  end())` for the JSON value.
-
-  @return The return value depends on the different types and is
-          defined as follows:
-          Value type  | return value
-          ----------- | -------------
-          null        | `0` (same as `size()`)
-          boolean     | `1` (same as `size()`)
-          string      | `1` (same as `size()`)
-          number      | `1` (same as `size()`)
-          object      | result of function `object_t::max_size()`
-          array       | result of function `array_t::max_size()`
-
-  @complexity Constant, as long as @ref array_t and @ref object_t satisfy
-  the Container concept; that is, their `max_size()` functions have constant
-  complexity.
-
-  @requirement This function helps `basic_json` satisfying the
-  [Container](http://en.cppreference.com/w/cpp/concept/Container)
-  requirements:
-  - The complexity is constant.
-  - Has the semantics of returning `b.size()` where `b` is the largest
-    possible JSON value.
-
-  @liveexample{The following code calls `max_size()` on the different value
-  types. Note the output is implementation specific.,max_size}
-
-  @sa @ref size() -- returns the number of elements
-
-  @since version 1.0.0
-  */
-  size_type max_size() const noexcept
-  {
-    switch (m_type)
-    {
-      case value_t::array:
-      {
-        // delegate call to array_t::max_size()
-        return m_value.array->max_size();
-      }
-
-      case value_t::object:
-      {
-        // delegate call to object_t::max_size()
-        return m_value.object->max_size();
-      }
-
-      default:
-      {
-        // all other types have max_size() == size()
-        return size();
-      }
-    }
-  }
-
-  /// @}
-
-  ///////////////
-  // modifiers //
-  ///////////////
-
-  /// @name modifiers
-  /// @{
-
-  /*!
-  @brief clears the contents
-
-  Clears the content of a JSON value and resets it to the default value as
-  if @ref basic_json(value_t) would have been called:
-
-  Value type  | initial value
-  ----------- | -------------
-  null        | `null`
-  boolean     | `false`
-  string      | `""`
-  number      | `0`
-  object      | `{}`
-  array       | `[]`
-
-  @complexity Linear in the size of the JSON value.
-
-  @liveexample{The example below shows the effect of `clear()` to different
-  JSON types.,clear}
-
-  @since version 1.0.0
-  */
-  void clear() noexcept
-  {
-    switch (m_type)
-    {
-      case value_t::number_integer:
-      {
-        m_value.number_integer = 0;
-        break;
-      }
-
-      case value_t::number_unsigned:
-      {
-        m_value.number_unsigned = 0;
-        break;
-      }
-
-      case value_t::number_float:
-      {
-        m_value.number_float = 0.0;
-        break;
-      }
-
-      case value_t::boolean:
-      {
-        m_value.boolean = false;
-        break;
-      }
-
-      case value_t::string:
-      {
-        m_value.string->clear();
-        break;
-      }
-
-      case value_t::array:
-      {
-        m_value.array->clear();
-        break;
-      }
-
-      case value_t::object:
-      {
-        m_value.object->clear();
-        break;
-      }
-
-      default:
-      {
-        break;
-      }
-    }
-  }
-
-  /*!
-  @brief add an object to an array
-
-  Appends the given element @a val to the end of the JSON value. If the
-  function is called on a JSON null value, an empty array is created before
-  appending @a val.
-
-  @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
-  null; example: `"cannot use push_back() with number"`
-
-  @complexity Amortized constant.
-
-  @liveexample{The example shows how `push_back()` and `+=` can be used to
-  add elements to a JSON array. Note how the `null` value was silently
-  converted to a JSON array.,push_back}
-
-  @since version 1.0.0
-  */
-  void push_back(basic_json &&val)
-  {
-    // 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()));
-    }
-
-    // transform null object into an array
-    if (is_null())
-    {
-      m_type = value_t::array;
-      m_value = value_t::array;
-      assert_invariant();
-    }
-
-    // add element to array (move semantics)
-    m_value.array->push_back(std::move(val));
-    // invalidate object
-    val.m_type = value_t::null;
-  }
-
-  /*!
-  @brief add an object to an array
-  @copydoc push_back(basic_json&&)
-  */
-  reference operator+=(basic_json &&val)
-  {
-    push_back(std::move(val));
-    return *this;
-  }
-
-  /*!
-  @brief add an object to an array
-  @copydoc push_back(basic_json&&)
-  */
-  void push_back(const basic_json &val)
-  {
-    // push_back only works for null objects or arrays
-    if (not(is_null() or is_array()))
+    @since version 1.0.0
+    */
+    reference operator[](size_type idx)
     {
-      JSON_THROW(
-          std::domain_error("cannot use push_back() with " + type_name()));
-    }
+        // implicitly convert null value to an empty array
+        if (is_null())
+        {
+            m_type = value_t::array;
+            m_value.array = create<array_t>();
+            assert_invariant();
+        }
 
-    // transform null object into an array
-    if (is_null())
-    {
-      m_type = value_t::array;
-      m_value = value_t::array;
-      assert_invariant();
-    }
+        // operator[] only works for arrays
+        if (is_array())
+        {
+            // fill up array with null values if given idx is outside range
+            if (idx >= m_value.array->size())
+            {
+                m_value.array->insert(m_value.array->end(),
+                                      idx - m_value.array->size() + 1,
+                                      basic_json());
+            }
 
-    // add element to array
-    m_value.array->push_back(val);
-  }
+            return m_value.array->operator[](idx);
+        }
+
+        JSON_THROW(
+            std::domain_error("cannot use operator[] with " + type_name()));
+    }
 
-  /*!
-  @brief add an object to an array
-  @copydoc push_back(basic_json&&)
-  */
-  reference operator+=(const basic_json &val)
-  {
-    push_back(val);
-    return *this;
-  }
+    /*!
+    @brief access specified array element
 
-  /*!
-  @brief add an object to an object
+    Returns a const reference to the element at specified location @a idx.
 
-  Inserts the given element @a val to the JSON object. If the function is
-  called on a JSON null value, an empty object is created before inserting
-  @a val.
+    @param[in] idx  index of the element to access
 
-  @param[in] val the value to add to the JSON object
+    @return const reference to the element at index @a idx
 
-  @throw std::domain_error when called on a type other than JSON object or
-  null; example: `"cannot use push_back() with number"`
+    @throw std::domain_error if JSON is not an array; example: `"cannot use
+    operator[] with null"`
 
-  @complexity Logarithmic in the size of the container, O(log(`size()`)).
+    @complexity Constant.
 
-  @liveexample{The example shows how `push_back()` and `+=` can be used to
-  add elements to a JSON object. Note how the `null` value was silently
-  converted to a JSON object.,push_back__object_t__value}
+    @liveexample{The example below shows how array elements can be read using
+    the `[]` operator.,operatorarray__size_type_const}
 
-  @since version 1.0.0
-  */
-  void push_back(const typename object_t::value_type &val)
-  {
-    // push_back only works for null objects or objects
-    if (not(is_null() or is_object()))
+    @since version 1.0.0
+    */
+    const_reference operator[](size_type idx) const
     {
-      JSON_THROW(
-          std::domain_error("cannot use push_back() with " + type_name()));
-    }
+        // const operator[] only works for arrays
+        if (is_array())
+        {
+            return m_value.array->operator[](idx);
+        }
 
-    // transform null object into an object
-    if (is_null())
-    {
-      m_type = value_t::object;
-      m_value = value_t::object;
-      assert_invariant();
+        JSON_THROW(
+            std::domain_error("cannot use operator[] with " + type_name()));
     }
 
-    // add element to array
-    m_value.object->insert(val);
-  }
+    /*!
+    @brief access specified object element
 
-  /*!
-  @brief add an object to an object
-  @copydoc push_back(const typename object_t::value_type&)
-  */
-  reference operator+=(const typename object_t::value_type &val)
-  {
-    push_back(val);
-    return *this;
-  }
+    Returns a reference to the element at with specified key @a key.
 
-  /*!
-  @brief add an object to an object
+    @note If @a key is not found in the object, then it is silently added to
+    the object and filled with a `null` value to make `key` a valid reference.
+    In case the value was `null` before, it is converted to an object.
 
-  This function allows to use `push_back` with an initializer list. In case
+    @param[in] key  key of the element to access
 
-  1. the current value is an object,
-  2. the initializer list @a init contains only two elements, and
-  3. the first element of @a init is a string,
+    @return reference to the element at key @a key
 
-  @a init is converted into an object element and added using
-  @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&&).
+    @throw std::domain_error if JSON is not an object or null; example:
+    `"cannot use operator[] with string"`
 
-  @param init  an initializer list
+    @complexity Logarithmic in the size of the container.
 
-  @complexity Linear in the size of the initializer list @a init.
+    @liveexample{The example below shows how object elements can be read and
+    written using the `[]` operator.,operatorarray__key_type}
 
-  @note This function is required to resolve an ambiguous overload error,
-        because pairs like `{"key", "value"}` can be both interpreted as
-        `object_t::value_type` or `std::initializer_list<basic_json>`, see
-        https://github.com/nlohmann/json/issues/235 for more information.
+    @sa @ref at(const typename object_t::key_type&) for access by reference
+    with range checking
+    @sa @ref value() for access by value with a default value
 
-  @liveexample{The example shows how initializer lists are treated as
-  objects when possible.,push_back__initializer_list}
-  */
-  void push_back(std::initializer_list<basic_json> init)
-  {
-    if (is_object() and init.size() == 2 and init.begin()->is_string())
-    {
-      const string_t key = *init.begin();
-      push_back(typename object_t::value_type(key, *(init.begin() + 1)));
-    }
-    else
+    @since version 1.0.0
+    */
+    reference operator[](const typename object_t::key_type &key)
     {
-      push_back(basic_json(init));
+        // implicitly convert null value to an empty object
+        if (is_null())
+        {
+            m_type = value_t::object;
+            m_value.object = create<object_t>();
+            assert_invariant();
+        }
+
+        // operator[] only works for objects
+        if (is_object())
+        {
+            return m_value.object->operator[](key);
+        }
+
+        JSON_THROW(
+            std::domain_error("cannot use operator[] with " + type_name()));
     }
-  }
 
-  /*!
-  @brief add an object to an object
-  @copydoc push_back(std::initializer_list<basic_json>)
-  */
-  reference operator+=(std::initializer_list<basic_json> init)
-  {
-    push_back(init);
-    return *this;
-  }
+    /*!
+    @brief read-only access specified object element
+
+    Returns a const reference to the element at with specified key @a key. No
+    bounds checking is performed.
 
-  /*!
-  @brief add an object to an array
+    @warning If the element with key @a key does not exist, the behavior is
+    undefined.
 
-  Creates a JSON value from the passed parameters @a args to the end of the
-  JSON value. If the function is called on a JSON null value, an empty array
-  is created before appending the value created from @a args.
+    @param[in] key  key of the element to access
 
-  @param[in] args arguments to forward to a constructor of @ref basic_json
-  @tparam Args compatible types to create a @ref basic_json object
+    @return const reference to the element at key @a key
 
-  @throw std::domain_error when called on a type other than JSON array or
-  null; example: `"cannot use emplace_back() with number"`
+    @pre The element with key @a key must exist. **This precondition is
+         enforced with an assertion.**
 
-  @complexity Amortized constant.
+    @throw std::domain_error if JSON is not an object; example: `"cannot use
+    operator[] with null"`
 
-  @liveexample{The example shows how `push_back()` can be used to add
-  elements to a JSON array. Note how the `null` value was silently converted
-  to a JSON array.,emplace_back}
+    @complexity Logarithmic in the size of the container.
 
-  @since version 2.0.8
-  */
-  template <class... Args> void emplace_back(Args &&... args)
-  {
-    // 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()));
-    }
+    @liveexample{The example below shows how object elements can be read using
+    the `[]` operator.,operatorarray__key_type_const}
+
+    @sa @ref at(const typename object_t::key_type&) for access by reference
+    with range checking
+    @sa @ref value() for access by value with a default value
 
-    // transform null object into an array
-    if (is_null())
+    @since version 1.0.0
+    */
+    const_reference operator[](const typename object_t::key_type &key) const
     {
-      m_type = value_t::array;
-      m_value = value_t::array;
-      assert_invariant();
+        // const operator[] only works for objects
+        if (is_object())
+        {
+            assert(m_value.object->find(key) != m_value.object->end());
+            return m_value.object->find(key)->second;
+        }
+
+        JSON_THROW(
+            std::domain_error("cannot use operator[] with " + type_name()));
     }
 
-    // add element to array (perfect forwarding)
-    m_value.array->emplace_back(std::forward<Args>(args)...);
-  }
+    /*!
+    @brief access specified object element
 
-  /*!
-  @brief add an object to an object if key does not exist
+    Returns a reference to the element at with specified key @a key.
 
-  Inserts a new element into a JSON object constructed in-place with the
-  given @a args if there is no element with the key in the container. If the
-  function is called on a JSON null value, an empty object is created before
-  appending the value created from @a args.
+    @note If @a key is not found in the object, then it is silently added to
+    the object and filled with a `null` value to make `key` a valid reference.
+    In case the value was `null` before, it is converted to an object.
 
-  @param[in] args arguments to forward to a constructor of @ref basic_json
-  @tparam Args compatible types to create a @ref basic_json object
+    @param[in] key  key of the element to access
 
-  @return a pair consisting of an iterator to the inserted element, or the
-          already-existing element if no insertion happened, and a bool
-          denoting whether the insertion took place.
+    @return reference to the element at key @a key
 
-  @throw std::domain_error when called on a type other than JSON object or
-  null; example: `"cannot use emplace() with number"`
+    @throw std::domain_error if JSON is not an object or null; example:
+    `"cannot use operator[] with string"`
 
-  @complexity Logarithmic in the size of the container, O(log(`size()`)).
+    @complexity Logarithmic in the size of the container.
 
-  @liveexample{The example shows how `emplace()` can be used to add elements
-  to a JSON object. Note how the `null` value was silently converted to a
-  JSON object. Further note how no value is added if there was already one
-  value stored with the same key.,emplace}
+    @liveexample{The example below shows how object elements can be read and
+    written using the `[]` operator.,operatorarray__key_type}
 
-  @since version 2.0.8
-  */
-  template <class... Args> std::pair<iterator, bool> emplace(Args &&... args)
-  {
-    // 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()));
-    }
+    @sa @ref at(const typename object_t::key_type&) for access by reference
+    with range checking
+    @sa @ref value() for access by value with a default value
 
-    // transform null object into an object
-    if (is_null())
+    @since version 1.0.0
+    */
+    template <typename T, std::size_t n>
+    reference operator[](T *(&key)[n])
     {
-      m_type = value_t::object;
-      m_value = value_t::object;
-      assert_invariant();
+        return operator[](static_cast<const T>(key));
     }
 
-    // add element to array (perfect forwarding)
-    auto res = m_value.object->emplace(std::forward<Args>(args)...);
-    // create result iterator and set iterator to the result of emplace
-    auto it = begin();
-    it.m_it.object_iterator = res.first;
+    /*!
+    @brief read-only access specified object element
+
+    Returns a const reference to the element at with specified key @a key. No
+    bounds checking is performed.
 
-    // return pair of iterator and boolean
-    return {it, res.second};
-  }
+    @warning If the element with key @a key does not exist, the behavior is
+    undefined.
 
-  /*!
-  @brief inserts element
+    @note This function is required for compatibility reasons with Clang.
 
-  Inserts element @a val before iterator @a pos.
+    @param[in] key  key of the element to access
 
-  @param[in] pos iterator before which the content will be inserted; may be
-  the end() iterator
-  @param[in] val element to insert
-  @return iterator pointing to the inserted @a val.
+    @return const reference to the element at key @a key
 
-  @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 JSON is not an object; example: `"cannot use
+    operator[] with null"`
 
-  @complexity Constant plus linear in the distance between pos and end of
-  the container.
+    @complexity Logarithmic in the size of the container.
 
-  @liveexample{The example shows how `insert()` is used.,insert}
+    @liveexample{The example below shows how object elements can be read using
+    the `[]` operator.,operatorarray__key_type_const}
 
-  @since version 1.0.0
-  */
-  iterator insert(const_iterator pos, const basic_json &val)
-  {
-    // insert only works for arrays
-    if (is_array())
-    {
-      // 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"));
-      }
+    @sa @ref at(const typename object_t::key_type&) for access by reference
+    with range checking
+    @sa @ref value() for access by value with a default value
 
-      // insert to array and return iterator
-      iterator result(this);
-      result.m_it.array_iterator =
-          m_value.array->insert(pos.m_it.array_iterator, val);
-      return result;
+    @since version 1.0.0
+    */
+    template <typename T, std::size_t n>
+    const_reference operator[](T *(&key)[n]) const
+    {
+        return operator[](static_cast<const T>(key));
     }
 
-    JSON_THROW(std::domain_error("cannot use insert() with " + type_name()));
-  }
+    /*!
+    @brief access specified object element
+
+    Returns a reference to the element at with specified key @a key.
 
-  /*!
-  @brief inserts element
-  @copydoc insert(const_iterator, const basic_json&)
-  */
-  iterator insert(const_iterator pos, basic_json &&val)
-  {
-    return insert(pos, val);
-  }
+    @note If @a key is not found in the object, then it is silently added to
+    the object and filled with a `null` value to make `key` a valid reference.
+    In case the value was `null` before, it is converted to an object.
 
-  /*!
-  @brief inserts elements
+    @param[in] key  key of the element to access
 
-  Inserts @a cnt copies of @a val before iterator @a pos.
+    @return reference to the element at key @a key
 
-  @param[in] pos iterator before which the content will be inserted; may be
-  the end() iterator
-  @param[in] cnt number of copies of @a val to insert
-  @param[in] val element to insert
-  @return iterator pointing to the first element inserted, or @a pos if
-  `cnt==0`
+    @throw std::domain_error if JSON is not an object or null; example:
+    `"cannot use operator[] with string"`
 
-  @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"`
+    @complexity Logarithmic in the size of the container.
 
-  @complexity Linear in @a cnt plus linear in the distance between @a pos
-  and end of the container.
+    @liveexample{The example below shows how object elements can be read and
+    written using the `[]` operator.,operatorarray__key_type}
 
-  @liveexample{The example shows how `insert()` is used.,insert__count}
+    @sa @ref at(const typename object_t::key_type&) for access by reference
+    with range checking
+    @sa @ref value() for access by value with a default value
 
-  @since version 1.0.0
-  */
-  iterator insert(const_iterator pos, size_type cnt, const basic_json &val)
-  {
-    // insert only works for arrays
-    if (is_array())
+    @since version 1.1.0
+    */
+    template <typename T>
+    reference operator[](T *key)
     {
-      // 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"));
-      }
+        // implicitly convert null to object
+        if (is_null())
+        {
+            m_type = value_t::object;
+            m_value = value_t::object;
+            assert_invariant();
+        }
+
+        // at only works for objects
+        if (is_object())
+        {
+            return m_value.object->operator[](key);
+        }
 
-      // insert to array and return iterator
-      iterator result(this);
-      result.m_it.array_iterator =
-          m_value.array->insert(pos.m_it.array_iterator, cnt, val);
-      return result;
+        JSON_THROW(
+            std::domain_error("cannot use operator[] with " + type_name()));
     }
 
-    JSON_THROW(std::domain_error("cannot use insert() with " + type_name()));
-  }
+    /*!
+    @brief read-only access specified object element
 
-  /*!
-  @brief inserts elements
+    Returns a const reference to the element at with specified key @a key. No
+    bounds checking is performed.
 
-  Inserts elements from range `[first, last)` before iterator @a pos.
+    @warning If the element with key @a key does not exist, the behavior is
+    undefined.
 
-  @param[in] pos iterator before which the content will be inserted; may be
-  the end() iterator
-  @param[in] first begin of the range of elements to insert
-  @param[in] last end of the range of elements to insert
+    @param[in] key  key of the element to access
 
-  @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
-  container for which insert is called; example: `"passed iterators may not
-  belong to container"`
+    @return const reference to the element at key @a key
 
-  @return iterator pointing to the first element inserted, or @a pos if
-  `first==last`
+    @pre The element with key @a key must exist. **This precondition is
+         enforced with an assertion.**
 
-  @complexity Linear in `std::distance(first, last)` plus linear in the
-  distance between @a pos and end of the container.
+    @throw std::domain_error if JSON is not an object; example: `"cannot use
+    operator[] with null"`
 
-  @liveexample{The example shows how `insert()` is used.,insert__range}
+    @complexity Logarithmic in the size of the container.
 
-  @since version 1.0.0
-  */
-  iterator insert(const_iterator pos, const_iterator first, const_iterator last)
-  {
-    // insert only works for arrays
-    if (not is_array())
-    {
-      JSON_THROW(std::domain_error("cannot use insert() with " + type_name()));
-    }
+    @liveexample{The example below shows how object elements can be read using
+    the `[]` operator.,operatorarray__key_type_const}
 
-    // 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"));
-    }
+    @sa @ref at(const typename object_t::key_type&) for access by reference
+    with range checking
+    @sa @ref value() for access by value with a default value
 
-    // check if range iterators belong to the same JSON object
-    if (first.m_object != last.m_object)
+    @since version 1.1.0
+    */
+    template <typename T>
+    const_reference operator[](T *key) const
     {
-      JSON_THROW(std::domain_error("iterators do not fit"));
+        // at only works for objects
+        if (is_object())
+        {
+            assert(m_value.object->find(key) != m_value.object->end());
+            return m_value.object->find(key)->second;
+        }
+
+        JSON_THROW(
+            std::domain_error("cannot use operator[] with " + type_name()));
     }
 
-    if (first.m_object == this or last.m_object == this)
-    {
-      JSON_THROW(
-          std::domain_error("passed iterators may not belong to container"));
+    /*!
+    @brief access specified object element with default value
+
+    Returns either a copy of an object's element at the specified key @a key
+    or a given default value if no element with key @a key exists.
+
+    The function is basically equivalent to executing
+    @code {.cpp}
+    try {
+        return at(key);
+    } catch(std::out_of_range) {
+        return default_value;
     }
+    @endcode
 
-    // insert to array and return iterator
-    iterator result(this);
-    result.m_it.array_iterator = m_value.array->insert(
-        pos.m_it.array_iterator, first.m_it.array_iterator,
-        last.m_it.array_iterator);
-    return result;
-  }
+    @note Unlike @ref at(const typename object_t::key_type&), this function
+    does not throw if the given key @a key was not found.
 
-  /*!
-  @brief inserts elements
+    @note Unlike @ref operator[](const typename object_t::key_type& key), this
+    function does not implicitly add an element to the position defined by @a
+    key. This function is furthermore also applicable to const objects.
 
-  Inserts elements from initializer list @a ilist before iterator @a pos.
+    @param[in] key  key of the element to access
+    @param[in] default_value  the value to return if @a key is not found
 
-  @param[in] pos iterator before which the content will be inserted; may be
-  the end() iterator
-  @param[in] ilist initializer list to insert the values from
+    @tparam ValueType type compatible to JSON values, for instance `int` for
+    JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for
+    JSON arrays. Note the type of the expected value at @a key and the default
+    value @a default_value must be compatible.
 
-  @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"`
+    @return copy of the element at key @a key or @a default_value if @a key
+    is not found
 
-  @return iterator pointing to the first element inserted, or @a pos if
-  `ilist` is empty
+    @throw std::domain_error if JSON is not an object; example: `"cannot use
+    value() with null"`
 
-  @complexity Linear in `ilist.size()` plus linear in the distance between
-  @a pos and end of the container.
+    @complexity Logarithmic in the size of the container.
 
-  @liveexample{The example shows how `insert()` is used.,insert__ilist}
+    @liveexample{The example below shows how object elements can be queried
+    with a default value.,basic_json__value}
 
-  @since version 1.0.0
-  */
-  iterator insert(const_iterator pos, std::initializer_list<basic_json> ilist)
-  {
-    // insert only works for arrays
-    if (not is_array())
-    {
-      JSON_THROW(std::domain_error("cannot use insert() with " + type_name()));
+    @sa @ref at(const typename object_t::key_type&) for access by reference
+    with range checking
+    @sa @ref operator[](const typename object_t::key_type&) for unchecked
+    access by reference
+
+    @since version 1.0.0
+    */
+    template <
+        class ValueType,
+        typename std::enable_if<
+            std::is_convertible<basic_json_t, ValueType>::value, int>::type = 0>
+    ValueType value(const typename object_t::key_type &key,
+                    ValueType default_value) const
+    {
+        // at only works for objects
+        if (is_object())
+        {
+            // if key is found, return value and given default value otherwise
+            const auto it = find(key);
+            if (it != end())
+            {
+                return *it;
+            }
+
+            return default_value;
+        }
+        else
+        {
+            JSON_THROW(
+                std::domain_error("cannot use value() with " + type_name()));
+        }
     }
 
-    // check if iterator pos fits to this JSON value
-    if (pos.m_object != this)
+    /*!
+    @brief overload for a default value of type const char*
+    @copydoc basic_json::value(const typename object_t::key_type&, ValueType)
+    const
+    */
+    string_t value(const typename object_t::key_type &key,
+                   const char *default_value) const
     {
-      JSON_THROW(std::domain_error("iterator does not fit current value"));
+        return value(key, string_t(default_value));
     }
 
-    // insert to array and return iterator
-    iterator result(this);
-    result.m_it.array_iterator =
-        m_value.array->insert(pos.m_it.array_iterator, ilist);
-    return result;
-  }
-
-  /*!
-  @brief exchanges the values
-
-  Exchanges the contents of the JSON value with those of @a other. Does not
-  invoke any move, copy, or swap operations on individual elements. All
-  iterators and references remain valid. The past-the-end iterator is
-  invalidated.
+    /*!
+    @brief access specified object element via JSON Pointer with default value
 
-  @param[in,out] other JSON value to exchange the contents with
+    Returns either a copy of an object's element at the specified key @a key
+    or a given default value if no element with key @a key exists.
 
-  @complexity Constant.
+    The function is basically equivalent to executing
+    @code {.cpp}
+    try {
+        return at(ptr);
+    } catch(std::out_of_range) {
+        return default_value;
+    }
+    @endcode
 
-  @liveexample{The example below shows how JSON values can be swapped with
-  `swap()`.,swap__reference}
+    @note Unlike @ref at(const json_pointer&), this function does not throw
+    if the given key @a key was not found.
 
-  @since version 1.0.0
-  */
-  void swap(reference other) noexcept(
-      std::is_nothrow_move_constructible<value_t>::value
-          and std::is_nothrow_move_assignable<value_t>::value
-              and std::is_nothrow_move_constructible<json_value>::value
-                  and std::is_nothrow_move_assignable<json_value>::value)
-  {
-    std::swap(m_type, other.m_type);
-    std::swap(m_value, other.m_value);
-    assert_invariant();
-  }
+    @param[in] ptr  a JSON pointer to the element to access
+    @param[in] default_value  the value to return if @a ptr found no value
 
-  /*!
-  @brief exchanges the values
+    @tparam ValueType type compatible to JSON values, for instance `int` for
+    JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for
+    JSON arrays. Note the type of the expected value at @a key and the default
+    value @a default_value must be compatible.
 
-  Exchanges the contents of a JSON array with those of @a other. Does not
-  invoke any move, copy, or swap operations on individual elements. All
-  iterators and references remain valid. The past-the-end iterator is
-  invalidated.
+    @return copy of the element at key @a key or @a default_value if @a key
+    is not found
 
-  @param[in,out] other array to exchange the contents with
+    @throw std::domain_error if JSON is not an object; example: `"cannot use
+    value() with null"`
 
-  @throw std::domain_error when JSON value is not an array; example:
-  `"cannot use swap() with string"`
+    @complexity Logarithmic in the size of the container.
 
-  @complexity Constant.
+    @liveexample{The example below shows how object elements can be queried
+    with a default value.,basic_json__value_ptr}
 
-  @liveexample{The example below shows how arrays can be swapped with
-  `swap()`.,swap__array_t}
+    @sa @ref operator[](const json_pointer&) for unchecked access by reference
 
-  @since version 1.0.0
-  */
-  void swap(array_t &other)
-  {
-    // swap only works for arrays
-    if (is_array())
+    @since version 2.0.2
+    */
+    template <
+        class ValueType,
+        typename std::enable_if<
+            std::is_convertible<basic_json_t, ValueType>::value, int>::type = 0>
+    ValueType value(const json_pointer &ptr, ValueType default_value) const
     {
-      std::swap(*(m_value.array), other);
+        // at only works for objects
+        if (is_object())
+        {
+            // 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_THROW(std::domain_error("cannot use value() with " + type_name()));
     }
-    else
+
+    /*!
+    @brief overload for a default value of type const char*
+    @copydoc basic_json::value(const json_pointer&, ValueType) const
+    */
+    string_t value(const json_pointer &ptr, const char *default_value) const
     {
-      JSON_THROW(std::domain_error("cannot use swap() with " + type_name()));
+        return value(ptr, string_t(default_value));
     }
-  }
 
-  /*!
-  @brief exchanges the values
+    /*!
+    @brief access the first element
 
-  Exchanges the contents of a JSON object with those of @a other. Does not
-  invoke any move, copy, or swap operations on individual elements. All
-  iterators and references remain valid. The past-the-end iterator is
-  invalidated.
+    Returns a reference to the first element in the container. For a JSON
+    container `c`, the expression `c.front()` is equivalent to `*c.begin()`.
 
-  @param[in,out] other object to exchange the contents with
+    @return In case of a structured type (array or object), a reference to the
+    first element is returned. In case of number, string, or boolean values, a
+    reference to the value is returned.
 
-  @throw std::domain_error when JSON value is not an object; example:
-  `"cannot use swap() with string"`
+    @complexity Constant.
 
-  @complexity Constant.
+    @pre The JSON value must not be `null` (would throw `std::out_of_range`)
+    or an empty array or object (undefined behavior, **guarded by
+    assertions**).
+    @post The JSON value remains unchanged.
 
-  @liveexample{The example below shows how objects can be swapped with
-  `swap()`.,swap__object_t}
+    @throw std::out_of_range when called on `null` value
 
-  @since version 1.0.0
-  */
-  void swap(object_t &other)
-  {
-    // swap only works for objects
-    if (is_object())
-    {
-      std::swap(*(m_value.object), other);
-    }
-    else
-    {
-      JSON_THROW(std::domain_error("cannot use swap() with " + type_name()));
-    }
-  }
+    @liveexample{The following code shows an example for `front()`.,front}
+
+    @sa @ref back() -- access the last element
+
+    @since version 1.0.0
+    */
+    reference front() { return *begin(); }
+
+    /*!
+    @copydoc basic_json::front()
+    */
+    const_reference front() const { return *cbegin(); }
+
+    /*!
+    @brief access the last element
+
+    Returns a reference to the last element in the container. For a JSON
+    container `c`, the expression `c.back()` is equivalent to
+    @code {.cpp}
+    auto tmp = c.end();
+    --tmp;
+    return *tmp;
+    @endcode
 
-  /*!
-  @brief exchanges the values
+    @return In case of a structured type (array or object), a reference to the
+    last element is returned. In case of number, string, or boolean values, a
+    reference to the value is returned.
 
-  Exchanges the contents of a JSON string with those of @a other. Does not
-  invoke any move, copy, or swap operations on individual elements. All
-  iterators and references remain valid. The past-the-end iterator is
-  invalidated.
+    @complexity Constant.
 
-  @param[in,out] other string to exchange the contents with
+    @pre The JSON value must not be `null` (would throw `std::out_of_range`)
+    or an empty array or object (undefined behavior, **guarded by
+    assertions**).
+    @post The JSON value remains unchanged.
 
-  @throw std::domain_error when JSON value is not a string; example: `"cannot
-  use swap() with boolean"`
+    @throw std::out_of_range when called on `null` value.
 
-  @complexity Constant.
+    @liveexample{The following code shows an example for `back()`.,back}
 
-  @liveexample{The example below shows how strings can be swapped with
-  `swap()`.,swap__string_t}
+    @sa @ref front() -- access the first element
 
-  @since version 1.0.0
-  */
-  void swap(string_t &other)
-  {
-    // swap only works for strings
-    if (is_string())
+    @since version 1.0.0
+    */
+    reference back()
     {
-      std::swap(*(m_value.string), other);
+        auto tmp = end();
+        --tmp;
+        return *tmp;
     }
-    else
+
+    /*!
+    @copydoc basic_json::back()
+    */
+    const_reference back() const
     {
-      JSON_THROW(std::domain_error("cannot use swap() with " + type_name()));
+        auto tmp = cend();
+        --tmp;
+        return *tmp;
     }
-  }
 
-  /// @}
+    /*!
+    @brief remove element given an iterator
 
-  //////////////////////////////////////////
-  // lexicographical comparison operators //
-  //////////////////////////////////////////
+    Removes the element specified by iterator @a pos. The iterator @a pos must
+    be valid and dereferenceable. Thus the `end()` iterator (which is valid,
+    but is not dereferenceable) cannot be used as a value for @a pos.
 
-  /// @name lexicographical comparison operators
-  /// @{
+    If called on a primitive type other than `null`, the resulting JSON value
+    will be `null`.
 
-private:
-  /*!
-  @brief comparison operator for JSON types
-
-  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
-  */
-  friend 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)
-    {
-      return false;
-    }
-
-    return order[static_cast<std::size_t>(lhs)] <
-           order[static_cast<std::size_t>(rhs)];
-  }
+    @param[in] pos iterator to the element to remove
+    @return Iterator following the last removed element. If the iterator @a
+    pos refers to the last element, the `end()` iterator is returned.
 
-public:
-  /*!
-  @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.
-  - 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.
-
-  @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:
+    @tparam IteratorType an @ref iterator or @ref const_iterator
+
+    @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
+    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
+    - strings: linear in the length of the string
+    - other types: constant
+
+    @liveexample{The example shows the result of `erase()` for different JSON
+    types.,erase__IteratorType}
+
+    @sa @ref erase(IteratorType, IteratorType) -- removes the elements in
+    the given range
+    @sa @ref erase(const typename object_t::key_type&) -- removes the element
+    from an object at the given key
+    @sa @ref erase(const size_type) -- removes the element from an array at
+    the given index
+
+    @since version 1.0.0
+    */
+    template <
+        class IteratorType,
+        typename std::enable_if<
+            std::is_same<IteratorType,
+                         typename basic_json_t::iterator>::value or
+                std::is_same<IteratorType,
+                             typename basic_json_t::const_iterator>::value,
+            int>::type = 0>
+    IteratorType erase(IteratorType pos)
+    {
+        // make sure iterator fits the current value
+        if (this != pos.m_object)
+        {
+            JSON_THROW(
+                std::domain_error("iterator does not fit current value"));
+        }
+
+        IteratorType result = end();
+
+        switch (m_type)
         {
-          return *lhs.m_value.array == *rhs.m_value.array;
+        case value_t::boolean:
+        case value_t::number_float:
+        case value_t::number_integer:
+        case value_t::number_unsigned:
+        case value_t::string:
+        {
+            if (not pos.m_it.primitive_iterator.is_begin())
+            {
+                JSON_THROW(std::out_of_range("iterator out of range"));
+            }
+
+            if (is_string())
+            {
+                AllocatorType<string_t> alloc;
+                alloc.destroy(m_value.string);
+                alloc.deallocate(m_value.string, 1);
+                m_value.string = nullptr;
+            }
+
+            m_type = value_t::null;
+            assert_invariant();
+            break;
         }
+
         case value_t::object:
         {
-          return *lhs.m_value.object == *rhs.m_value.object;
+            result.m_it.object_iterator =
+                m_value.object->erase(pos.m_it.object_iterator);
+            break;
         }
-        case value_t::null:
+
+        case value_t::array:
         {
-          return true;
+            result.m_it.array_iterator =
+                m_value.array->erase(pos.m_it.array_iterator);
+            break;
         }
-        case value_t::string:
+
+        default:
         {
-          return *lhs.m_value.string == *rhs.m_value.string;
+            JSON_THROW(
+                std::domain_error("cannot use erase() with " + type_name()));
         }
-        case value_t::boolean:
+        }
+
+        return result;
+    }
+
+    /*!
+    @brief remove elements given an iterator range
+
+    Removes the element specified by the range `[first; last)`. The iterator
+    @a first does not need to be dereferenceable if `first == last`: erasing
+    an empty range is a no-op.
+
+    If called on a primitive type other than `null`, the resulting JSON value
+    will be `null`.
+
+    @param[in] first iterator to the beginning of the range to remove
+    @param[in] last iterator past the end of the range to remove
+    @return Iterator following the last removed element. If the iterator @a
+    second refers to the last element, the `end()` iterator is returned.
+
+    @tparam IteratorType an @ref iterator or @ref const_iterator
+
+    @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
+    iterators (i.e., if `first != begin()` and `last != end()`); example:
+    `"iterators out of range"`
+
+    @complexity The complexity depends on the type:
+    - objects: `log(size()) + std::distance(first, last)`
+    - arrays: linear in the distance between @a first and @a last, plus linear
+      in the distance between @a last and end of the container
+    - strings: linear in the length of the string
+    - other types: constant
+
+    @liveexample{The example shows the result of `erase()` for different JSON
+    types.,erase__IteratorType_IteratorType}
+
+    @sa @ref erase(IteratorType) -- removes the element at a given position
+    @sa @ref erase(const typename object_t::key_type&) -- removes the element
+    from an object at the given key
+    @sa @ref erase(const size_type) -- removes the element from an array at
+    the given index
+
+    @since version 1.0.0
+    */
+    template <
+        class IteratorType,
+        typename std::enable_if<
+            std::is_same<IteratorType,
+                         typename basic_json_t::iterator>::value or
+                std::is_same<IteratorType,
+                             typename basic_json_t::const_iterator>::value,
+            int>::type = 0>
+    IteratorType erase(IteratorType first, IteratorType last)
+    {
+        // make sure iterator fits the current value
+        if (this != first.m_object or this != last.m_object)
         {
-          return lhs.m_value.boolean == rhs.m_value.boolean;
+            JSON_THROW(std::domain_error("iterators do not fit current value"));
         }
+
+        IteratorType result = end();
+
+        switch (m_type)
+        {
+        case value_t::boolean:
+        case value_t::number_float:
         case value_t::number_integer:
+        case value_t::number_unsigned:
+        case value_t::string:
         {
-          return lhs.m_value.number_integer == rhs.m_value.number_integer;
+            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"));
+            }
+
+            if (is_string())
+            {
+                AllocatorType<string_t> alloc;
+                alloc.destroy(m_value.string);
+                alloc.deallocate(m_value.string, 1);
+                m_value.string = nullptr;
+            }
+
+            m_type = value_t::null;
+            assert_invariant();
+            break;
         }
-        case value_t::number_unsigned:
+
+        case value_t::object:
         {
-          return lhs.m_value.number_unsigned == rhs.m_value.number_unsigned;
+            result.m_it.object_iterator = m_value.object->erase(
+                first.m_it.object_iterator, last.m_it.object_iterator);
+            break;
         }
-        case value_t::number_float:
+
+        case value_t::array:
         {
-          return lhs.m_value.number_float == rhs.m_value.number_float;
+            result.m_it.array_iterator = m_value.array->erase(
+                first.m_it.array_iterator, last.m_it.array_iterator);
+            break;
         }
+
         default:
         {
-          return false;
+            JSON_THROW(
+                std::domain_error("cannot use erase() with " + type_name()));
+        }
         }
-      }
-    }
-    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
-
-  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()`.
 
-  @param[in] v  JSON value to consider
-  @return whether @a v is null
+        return result;
+    }
 
-  @complexity Constant.
+    /*!
+    @brief remove element from a JSON object given a key
 
-  @liveexample{The example compares several JSON types to the null pointer.
-  ,operator__equal__nullptr_t}
+    Removes elements from a JSON object with the key value @a key.
 
-  @since version 1.0.0
-  */
-  friend bool operator==(const_reference v, std::nullptr_t) noexcept
-  {
-    return v.is_null();
-  }
+    @param[in] key value of the elements to remove
 
-  /*!
-  @brief comparison: equal
-  @copydoc operator==(const_reference, std::nullptr_t)
-  */
-  friend bool operator==(std::nullptr_t, const_reference v) noexcept
-  {
-    return v.is_null();
-  }
+    @return Number of elements removed. If @a ObjectType is the default
+    `std::map` type, the return value will always be `0` (@a key was not
+    found) or `1` (@a key was found).
 
-  /*!
-  @brief comparison: not equal
+    @post References and iterators to the erased elements are invalidated.
+    Other references and iterators are not affected.
 
-  Compares two JSON values for inequality by calculating `not (lhs == rhs)`.
+    @throw std::domain_error when called on a type other than JSON object;
+    example: `"cannot use erase() with null"`
 
-  @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
+    @complexity `log(size()) + count(key)`
 
-  @complexity Linear.
+    @liveexample{The example shows the effect of `erase()`.,erase__key_type}
 
-  @liveexample{The example demonstrates comparing several JSON
-  types.,operator__notequal}
+    @sa @ref erase(IteratorType) -- removes the element at a given position
+    @sa @ref erase(IteratorType, IteratorType) -- removes the elements in
+    the given range
+    @sa @ref erase(const size_type) -- removes the element from an array at
+    the given index
 
-  @since version 1.0.0
-  */
-  friend bool operator!=(const_reference lhs, const_reference rhs) noexcept
-  {
-    return not(lhs == rhs);
-  }
+    @since version 1.0.0
+    */
+    size_type erase(const typename object_t::key_type &key)
+    {
+        // this erase only works for objects
+        if (is_object())
+        {
+            return m_value.object->erase(key);
+        }
 
-  /*!
-  @brief comparison: not equal
+        JSON_THROW(std::domain_error("cannot use erase() with " + type_name()));
+    }
 
-  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 remove element from a JSON array given an index
 
-  @param[in] v  JSON value to consider
-  @return whether @a v is not null
+    Removes element from a JSON array at the index @a idx.
 
-  @complexity Constant.
+    @param[in] idx index of the element to remove
 
-  @liveexample{The example compares several JSON types to the null pointer.
-  ,operator__notequal__nullptr_t}
+    @throw std::domain_error when called on a type other than JSON array;
+    example: `"cannot use erase() with null"`
+    @throw std::out_of_range when `idx >= size()`; example: `"array index 17
+    is out of range"`
 
-  @since version 1.0.0
-  */
-  friend bool operator!=(const_reference v, std::nullptr_t) noexcept
-  {
-    return not v.is_null();
-  }
+    @complexity Linear in distance between @a idx and the end of the container.
 
-  /*!
-  @brief comparison: not equal
-  @copydoc operator!=(const_reference, std::nullptr_t)
-  */
-  friend bool operator!=(std::nullptr_t, const_reference v) noexcept
-  {
-    return not v.is_null();
-  }
+    @liveexample{The example shows the effect of `erase()`.,erase__size_type}
 
-  /*!
-  @brief comparison: less than
+    @sa @ref erase(IteratorType) -- removes the element at a given position
+    @sa @ref erase(IteratorType, IteratorType) -- removes the elements in
+    the given range
+    @sa @ref erase(const typename object_t::key_type&) -- removes the element
+    from an object at the given key
 
-  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
-
-  @complexity Linear.
-
-  @liveexample{The example demonstrates comparing several JSON
-  types.,operator__less}
-
-  @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 false;
-        }
-        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:
+    @since version 1.0.0
+    */
+    void erase(const size_type idx)
+    {
+        // this erase only works for arrays
+        if (is_array())
         {
-          return lhs.m_value.number_float < rhs.m_value.number_float;
+            if (idx >= size())
+            {
+                JSON_THROW(std::out_of_range(
+                    "array index " + std::to_string(idx) + " is out of range"));
+            }
+
+            m_value.array->erase(m_value.array->begin() +
+                                 static_cast<difference_type>(idx));
         }
-        default:
+        else
         {
-          return false;
+            JSON_THROW(
+                std::domain_error("cannot use erase() with " + type_name()));
         }
-      }
     }
-    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)
+
+    /// @}
+
+    ////////////
+    // lookup //
+    ////////////
+
+    /// @name lookup
+    /// @{
+
+    /*!
+    @brief find an element in a JSON object
+
+    Finds an element in a JSON object with key equivalent to @a key. If the
+    element is not found or the JSON value is not an object, end() is
+    returned.
+
+    @note This method always returns @ref end() when executed on a JSON type
+          that is not an object.
+
+    @param[in] key key value of the element to search for
+
+    @return Iterator to an element with key equivalent to @a key. If no such
+    element is found or the JSON value is not an object, past-the-end (see
+    @ref end()) iterator is returned.
+
+    @complexity Logarithmic in the size of the JSON object.
+
+    @liveexample{The example shows how `find()` is used.,find__key_type}
+
+    @since version 1.0.0
+    */
+    iterator find(typename object_t::key_type key)
     {
-      return lhs.m_value.number_integer <
-             static_cast<number_integer_t>(rhs.m_value.number_unsigned);
+        auto result = end();
+
+        if (is_object())
+        {
+            result.m_it.object_iterator = m_value.object->find(key);
+        }
+
+        return result;
     }
-    else if (lhs_type == value_t::number_unsigned and
-             rhs_type == value_t::number_integer)
+
+    /*!
+    @brief find an element in a JSON object
+    @copydoc find(typename object_t::key_type)
+    */
+    const_iterator find(typename object_t::key_type key) const
     {
-      return static_cast<number_integer_t>(lhs.m_value.number_unsigned) <
-             rhs.m_value.number_integer;
+        auto result = cend();
+
+        if (is_object())
+        {
+            result.m_it.object_iterator = m_value.object->find(key);
+        }
+
+        return result;
     }
 
-    // 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 returns the number of occurrences of a key in a JSON object
 
-  /*!
-  @brief comparison: less than or equal
+    Returns the number of elements with key @a key. If ObjectType is the
+    default `std::map` type, the return value will always be `0` (@a key was
+    not found) or `1` (@a key was found).
 
-  Compares whether one JSON value @a lhs is less than or equal to another
-  JSON value by calculating `not (rhs < lhs)`.
+    @note This method always returns `0` when executed on a JSON type that is
+          not an object.
 
-  @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
+    @param[in] key key value of the element to count
 
-  @complexity Linear.
+    @return Number of elements with key @a key. If the JSON value is not an
+    object, the return value will be `0`.
 
-  @liveexample{The example demonstrates comparing several JSON
-  types.,operator__greater}
+    @complexity Logarithmic in the size of the JSON object.
 
-  @since version 1.0.0
-  */
-  friend bool operator<=(const_reference lhs, const_reference rhs) noexcept
-  {
-    return not(rhs < lhs);
-  }
+    @liveexample{The example shows how `count()` is used.,count}
 
-  /*!
-  @brief comparison: greater than
+    @since version 1.0.0
+    */
+    size_type count(typename object_t::key_type key) const
+    {
+        // return 0 for all nonobject types
+        return is_object() ? m_value.object->count(key) : 0;
+    }
 
-  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 @a lhs is greater than to @a rhs
+    ///////////////
+    // iterators //
+    ///////////////
 
-  @complexity Linear.
+    /// @name iterators
+    /// @{
 
-  @liveexample{The example demonstrates comparing several JSON
-  types.,operator__lessequal}
+    /*!
+    @brief returns an iterator to the first element
 
-  @since version 1.0.0
-  */
-  friend bool operator>(const_reference lhs, const_reference rhs) noexcept
-  {
-    return not(lhs <= rhs);
-  }
+    Returns an iterator to the first element.
 
-  /*!
-  @brief comparison: greater than or equal
+    @image html range-begin-end.svg "Illustration from cppreference.com"
 
-  Compares whether one JSON value @a lhs is greater than or equal to another
-  JSON value by calculating `not (lhs < rhs)`.
+    @return iterator to the first element
 
-  @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 Constant.
 
-  @complexity Linear.
+    @requirement This function helps `basic_json` satisfying the
+    [Container](http://en.cppreference.com/w/cpp/concept/Container)
+    requirements:
+    - The complexity is constant.
 
-  @liveexample{The example demonstrates comparing several JSON
-  types.,operator__greaterequal}
+    @liveexample{The following code shows an example for `begin()`.,begin}
 
-  @since version 1.0.0
-  */
-  friend bool operator>=(const_reference lhs, const_reference rhs) noexcept
-  {
-    return not(lhs < rhs);
-  }
+    @sa @ref cbegin() -- returns a const iterator to the beginning
+    @sa @ref end() -- returns an iterator to the end
+    @sa @ref cend() -- returns a const iterator to the end
 
-  /// @}
+    @since version 1.0.0
+    */
+    iterator begin() noexcept
+    {
+        iterator result(this);
+        result.set_begin();
+        return result;
+    }
 
-  ///////////////////
-  // serialization //
-  ///////////////////
+    /*!
+    @copydoc basic_json::cbegin()
+    */
+    const_iterator begin() const noexcept { return cbegin(); }
 
-  /// @name serialization
-  /// @{
+    /*!
+    @brief returns a const iterator to the first element
 
-  /*!
-  @brief serialize to stream
+    Returns a const iterator to the first element.
 
-  Serialize the given JSON value @a j to the output stream @a o. The JSON
-  value will be serialized using the @ref dump member function. The
-  indentation of the output can be controlled with the member variable
-  `width` of the output stream @a o. For instance, using the manipulator
-  `std::setw(4)` on @a o sets the indentation level to `4` and the
-  serialization result is the same as calling `dump(4)`.
+    @image html range-begin-end.svg "Illustration from cppreference.com"
 
-  @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.
+    @return const iterator to the first element
 
-  @param[in,out] o  stream to serialize to
-  @param[in] j  JSON value to serialize
+    @complexity Constant.
 
-  @return the stream @a o
+    @requirement This function helps `basic_json` satisfying the
+    [Container](http://en.cppreference.com/w/cpp/concept/Container)
+    requirements:
+    - The complexity is constant.
+    - Has the semantics of `const_cast<const basic_json&>(*this).begin()`.
 
-  @complexity Linear.
+    @liveexample{The following code shows an example for `cbegin()`.,cbegin}
 
-  @liveexample{The example below shows the serialization with different
-  parameters to `width` to adjust the indentation level.,operator_serialize}
+    @sa @ref begin() -- returns an iterator to the beginning
+    @sa @ref end() -- returns an iterator to the end
+    @sa @ref cend() -- returns a const iterator to the end
 
-  @since version 1.0.0
-  */
-  friend std::ostream &operator<<(std::ostream &o, const basic_json &j)
-  {
-    // read width member and use it as indentation parameter if nonzero
-    const bool pretty_print = (o.width() > 0);
-    const auto indentation = (pretty_print ? o.width() : 0);
+    @since version 1.0.0
+    */
+    const_iterator cbegin() const noexcept
+    {
+        const_iterator result(this);
+        result.set_begin();
+        return result;
+    }
 
-    // reset width to 0 for subsequent calls to this stream
-    o.width(0);
+    /*!
+    @brief returns an iterator to one past the last element
 
-    // fix locale problems
-    const auto old_locale = o.imbue(std::locale::classic());
-    // set precision
+    Returns an iterator to one past the last element.
 
-    // 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);
+    @image html range-begin-end.svg "Illustration from cppreference.com"
 
-    // do the actual serialization
-    j.dump(o, pretty_print, static_cast<unsigned int>(indentation));
+    @return iterator one past the last element
 
-    // reset locale and precision
-    o.imbue(old_locale);
-    o.precision(old_precision);
-    return o;
-  }
-
-  /*!
-  @brief serialize to stream
-  @copydoc operator<<(std::ostream&, const basic_json&)
-  */
-  friend std::ostream &operator>>(const basic_json &j, std::ostream &o)
-  {
-    return o << j;
-  }
-
-  /// @}
+    @complexity Constant.
 
-  /////////////////////
-  // deserialization //
-  /////////////////////
+    @requirement This function helps `basic_json` satisfying the
+    [Container](http://en.cppreference.com/w/cpp/concept/Container)
+    requirements:
+    - The complexity is constant.
 
-  /// @name deserialization
-  /// @{
-
-  /*!
-  @brief deserialize from an array
-
-  This function reads from an array of 1-byte values.
-
-  @pre Each element of the container has a size of 1 byte. Violating this
-  precondition yields undefined behavior. **This precondition is enforced
-  with a static assertion.**
-
-  @param[in] array  array to read 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)
-
-  @return result of the deserialization
-
-  @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.
-
-  @liveexample{The example below demonstrates the `parse()` function reading
-  from an array.,parse__array__parser_callback_t}
-
-  @since version 2.0.3
-  */
-  template <class T, std::size_t N>
-  static basic_json parse(T (&array)[N], const parser_callback_t cb = nullptr)
-  {
-    // delegate the call to the iterator-range parse overload
-    return parse(std::begin(array), std::end(array), cb);
-  }
-
-  /*!
-  @brief deserialize from string literal
-
-  @tparam CharT character/literal type with size of 1 byte
-  @param[in] s  string literal 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)
-
-  @return result of the deserialization
-
-  @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.
-  @note String containers like `std::string` or @ref string_t can be parsed
-        with @ref parse(const ContiguousContainer&, const parser_callback_t)
-
-  @liveexample{The example below demonstrates the `parse()` function with
-  and without callback function.,parse__string__parser_callback_t}
-
-  @sa @ref parse(std::istream&, const parser_callback_t) for a version that
-  reads from an input stream
-
-  @since version 1.0.0 (originally for @ref string_t)
-  */
-  template <typename CharT,
-            typename std::enable_if<
-                std::is_pointer<CharT>::value and
-                    std::is_integral<
-                        typename std::remove_pointer<CharT>::type>::value and
-                    sizeof(typename std::remove_pointer<CharT>::type) == 1,
-                int>::type = 0>
-  static basic_json parse(const CharT s, const parser_callback_t cb = nullptr)
-  {
-    return parser(reinterpret_cast<const char *>(s), cb).parse();
-  }
-
-  /*!
-  @brief deserialize from 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)
-
-  @return result of the deserialization
-
-  @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.
-
-  @liveexample{The example below demonstrates the `parse()` function with
-  and without callback function.,parse__istream__parser_callback_t}
-
-  @sa @ref parse(const CharT, const parser_callback_t) for a version
-  that reads from a string
-
-  @since version 1.0.0
-  */
-  static basic_json parse(std::istream &i, const parser_callback_t cb = nullptr)
-  {
-    return parser(i, cb).parse();
-  }
-
-  /*!
-  @copydoc parse(std::istream&, const parser_callback_t)
-  */
-  static basic_json parse(std::istream &&i,
-                          const parser_callback_t cb = nullptr)
-  {
-    return parser(i, cb).parse();
-  }
-
-  /*!
-  @brief deserialize from an iterator range with contiguous storage
-
-  This function reads from an iterator range of a container with contiguous
-  storage of 1-byte values. Compatible container types include
-  `std::vector`, `std::string`, `std::array`, `std::valarray`, and
-  `std::initializer_list`. Furthermore, C-style arrays can be used with
-  `std::begin()`/`std::end()`. User-defined containers can be used as long
-  as they implement random-access iterators and a contiguous storage.
-
-  @pre The iterator range is contiguous. Violating this precondition yields
-  undefined behavior. **This precondition is enforced with an assertion.**
-  @pre Each element in the range has a size of 1 byte. Violating this
-  precondition yields undefined behavior. **This precondition is enforced
-  with a static assertion.**
-
-  @warning There is no way to enforce all preconditions at compile-time. If
-           the function is called with noncompliant iterators and with
-           assertions switched off, the behavior is undefined and will most
-           likely yield segmentation violation.
-
-  @tparam IteratorType iterator of container with contiguous storage
-  @param[in] first  begin of the range to parse (included)
-  @param[in] last  end of the range to parse (excluded)
-  @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)
-
-  @return result of the deserialization
-
-  @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.
-
-  @liveexample{The example below demonstrates the `parse()` function reading
-  from an iterator range.,parse__iteratortype__parser_callback_t}
-
-  @since version 2.0.3
-  */
-  template <class IteratorType,
-            typename std::enable_if<
-                std::is_base_of<std::random_access_iterator_tag,
-                                typename std::iterator_traits<
-                                    IteratorType>::iterator_category>::value,
-                int>::type = 0>
-  static basic_json parse(IteratorType first, IteratorType last,
-                          const parser_callback_t cb = nullptr)
-  {
-    // assertion to check that the iterator range is indeed contiguous,
-    // see http://stackoverflow.com/a/35008842/266378 for more discussion
-    assert(std::accumulate(
-               first, last, std::pair<bool, int>(true, 0),
-               [&first](std::pair<bool, int> res, decltype(*first) val) {
-                 res.first &=
-                     (val ==
-                      *(std::next(std::addressof(*first), res.second++)));
-                 return res;
-               })
-               .first);
-
-    // assertion to check that each element is 1 byte long
-    static_assert(
-        sizeof(typename std::iterator_traits<IteratorType>::value_type) == 1,
-        "each element in the iterator range must have the size of 1 byte");
-
-    // if iterator range is empty, create a parser with an empty string
-    // to generate "unexpected EOF" error message
-    if (std::distance(first, last) <= 0)
-    {
-      return parser("").parse();
-    }
-
-    return parser(first, last, cb).parse();
-  }
-
-  /*!
-  @brief deserialize from a container with contiguous storage
-
-  This function reads from a container with contiguous storage of 1-byte
-  values. Compatible container types include `std::vector`, `std::string`,
-  `std::array`, and `std::initializer_list`. User-defined containers can be
-  used as long as they implement random-access iterators and a contiguous
-  storage.
-
-  @pre The container storage is contiguous. Violating this precondition
-  yields undefined behavior. **This precondition is enforced with an
-  assertion.**
-  @pre Each element of the container has a size of 1 byte. Violating this
-  precondition yields undefined behavior. **This precondition is enforced
-  with a static assertion.**
-
-  @warning There is no way to enforce all preconditions at compile-time. If
-           the function is called with a noncompliant container and with
-           assertions switched off, the behavior is undefined and will most
-           likely yield segmentation violation.
-
-  @tparam ContiguousContainer container type with contiguous storage
-  @param[in] c  container to read 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)
-
-  @return result of the deserialization
-
-  @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.
-
-  @liveexample{The example below demonstrates the `parse()` function reading
-  from a contiguous container.,parse__contiguouscontainer__parser_callback_t}
-
-  @since version 2.0.3
-  */
-  template <
-      class ContiguousContainer,
-      typename std::enable_if<
-          not std::is_pointer<ContiguousContainer>::value and
-              std::is_base_of<std::random_access_iterator_tag,
-                              typename std::iterator_traits<decltype(std::begin(
-                                  std::declval<ContiguousContainer const>()))>::
-                                  iterator_category>::value,
-          int>::type = 0>
-  static basic_json parse(const ContiguousContainer &c,
-                          const parser_callback_t cb = nullptr)
-  {
-    // delegate the call to the iterator-range parse overload
-    return parse(std::begin(c), std::end(c), cb);
-  }
-
-  /*!
-  @brief deserialize from stream
-
-  Deserializes an input stream to a JSON value.
-
-  @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
-
-  @complexity Linear in the length of the input. The parser is a predictive
-  LL(1) parser.
-
-  @note A UTF-8 byte order mark is silently ignored.
-
-  @liveexample{The example below shows how a JSON value is constructed by
-  reading a serialization from a stream.,operator_deserialize}
-
-  @sa parse(std::istream&, const parser_callback_t) for a variant with a
-  parser callback function to filter values while parsing
-
-  @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();
-    return i;
-  }
-
-  /// @}
-
-  //////////////////////////////////////////
-  // binary serialization/deserialization //
-  //////////////////////////////////////////
-
-  /// @name binary serialization/deserialization support
-  /// @{
+    @liveexample{The following code shows an example for `end()`.,end}
 
-private:
-  template <typename T>
-  static void add_to_vector(std::vector<uint8_t> &vec, size_t bytes,
-                            const T number)
-  {
-    assert(bytes == 1 or bytes == 2 or bytes == 4 or bytes == 8);
+    @sa @ref cend() -- returns a const iterator to the end
+    @sa @ref begin() -- returns an iterator to the beginning
+    @sa @ref cbegin() -- returns a const iterator to the beginning
 
-    switch (bytes)
+    @since version 1.0.0
+    */
+    iterator end() noexcept
     {
-      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
-      }
+        iterator result(this);
+        result.set_end();
+        return result;
+    }
 
-      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
-      }
-
-      case 2:
-      {
-        vec.push_back(static_cast<uint8_t>((number >> 010) & 0xff));
-        // intentional fall-through
-      }
-
-      case 1:
-      {
-        vec.push_back(static_cast<uint8_t>(number & 0xff));
-        break;
-      }
-    }
-  }
-
-  /*!
-  @brief take sufficient bytes from a vector to fill an integer variable
-
-  In the context of binary serialization formats, we need to read several
-  bytes from a byte vector and combine them to multi-byte integral data
-  types.
-
-  @param[in] vec  byte vector to read from
-  @param[in] current_index  the position in the vector after which to read
-
-  @return the next sizeof(T) bytes from @a vec, in reverse order as T
-
-  @tparam T the integral return type
-
-  @throw std::out_of_range 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
-  the return value. In the figures below, let sizeof(T)=4 and `i` be the loop
-  variable.
-
-  Precondition:
-
-  vec:   |   |   | a | b | c | d |      T: |   |   |   |   |
-               ^               ^             ^                ^
-         current_index         i            ptr        sizeof(T)
-
-  Postcondition:
-
-  vec:   |   |   | a | b | c | d |      T: | d | c | b | a |
-               ^   ^                                     ^
-               |   i                                    ptr
-         current_index
-
-  @sa Code adapted from <http://stackoverflow.com/a/41031865/266378>.
-  */
-  template <typename T>
-  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"));
-    }
-
-    T result;
-    auto *ptr = reinterpret_cast<uint8_t *>(&result);
-    for (size_t i = 0; i < sizeof(T); ++i)
-    {
-      *ptr++ = vec[current_index + sizeof(T) - i];
-    }
-    return result;
-  }
-
-  /*!
-  @brief create a MessagePack serialization of a given JSON value
-
-  This is a straightforward implementation of the MessagePack specification.
-
-  @param[in] j  JSON value to serialize
-  @param[in,out] v  byte vector to write the serialization to
-
-  @sa https://github.com/msgpack/msgpack/blob/master/spec.md
-  */
-  static void to_msgpack_internal(const basic_json &j, std::vector<uint8_t> &v)
-  {
-    switch (j.type())
-    {
-      case value_t::null:
-      {
-        // nil
-        v.push_back(0xc0);
-        break;
-      }
-
-      case value_t::boolean:
-      {
-        // true and false
-        v.push_back(j.m_value.boolean ? 0xc3 : 0xc2);
-        break;
-      }
-
-      case value_t::number_integer:
-      {
-        if (j.m_value.number_integer >= 0)
-        {
-          // MessagePack does not differentiate between positive
-          // signed integers and unsigned integers. Therefore, we
-          // used the code from the value_t::number_unsigned case
-          // here.
-          if (j.m_value.number_unsigned < 128)
-          {
-            // positive fixnum
-            add_to_vector(v, 1, j.m_value.number_unsigned);
-          }
-          else if (j.m_value.number_unsigned <= UINT8_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)
-          {
-            // 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)
-          {
-            // 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)
-          {
-            // uint 64
-            v.push_back(0xcf);
-            add_to_vector(v, 8, j.m_value.number_unsigned);
-          }
-        }
-        else
-        {
-          if (j.m_value.number_integer >= -32)
-          {
-            // 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)
-          {
-            // 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)
-          {
-            // 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)
-          {
-            // 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)
-          {
-            // int 64
-            v.push_back(0xd3);
-            add_to_vector(v, 8, j.m_value.number_integer);
-          }
-        }
-        break;
-      }
-
-      case value_t::number_unsigned:
-      {
-        if (j.m_value.number_unsigned < 128)
-        {
-          // positive fixnum
-          add_to_vector(v, 1, j.m_value.number_unsigned);
-        }
-        else if (j.m_value.number_unsigned <= UINT8_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)
-        {
-          // 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)
-        {
-          // 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)
-        {
-          // uint 64
-          v.push_back(0xcf);
-          add_to_vector(v, 8, j.m_value.number_unsigned);
-        }
-        break;
-      }
+    /*!
+    @copydoc basic_json::cend()
+    */
+    const_iterator end() const noexcept { return cend(); }
 
-      case value_t::number_float:
-      {
-        // float 64
-        v.push_back(0xcb);
-        const auto *helper =
-            reinterpret_cast<const uint8_t *>(&(j.m_value.number_float));
-        for (size_t i = 0; i < 8; ++i)
-        {
-          v.push_back(helper[7 - i]);
-        }
-        break;
-      }
+    /*!
+    @brief returns a const iterator to one past the last element
 
-      case value_t::string:
-      {
-        const auto N = j.m_value.string->size();
-        if (N <= 31)
-        {
-          // fixstr
-          v.push_back(static_cast<uint8_t>(0xa0 | N));
-        }
-        else if (N <= 255)
-        {
-          // str 8
-          v.push_back(0xd9);
-          add_to_vector(v, 1, N);
-        }
-        else if (N <= 65535)
-        {
-          // str 16
-          v.push_back(0xda);
-          add_to_vector(v, 2, N);
-        }
-        else if (N <= 4294967295)
-        {
-          // str 32
-          v.push_back(0xdb);
-          add_to_vector(v, 4, N);
-        }
+    Returns a const iterator to one past the last element.
 
-        // append string
-        std::copy(j.m_value.string->begin(), j.m_value.string->end(),
-                  std::back_inserter(v));
-        break;
-      }
+    @image html range-begin-end.svg "Illustration from cppreference.com"
 
-      case value_t::array:
-      {
-        const auto N = j.m_value.array->size();
-        if (N <= 15)
-        {
-          // fixarray
-          v.push_back(static_cast<uint8_t>(0x90 | N));
-        }
-        else if (N <= 0xffff)
-        {
-          // array 16
-          v.push_back(0xdc);
-          add_to_vector(v, 2, N);
-        }
-        else if (N <= 0xffffffff)
-        {
-          // array 32
-          v.push_back(0xdd);
-          add_to_vector(v, 4, N);
-        }
+    @return const iterator one past the last element
 
-        // append each element
-        for (const auto &el : *j.m_value.array)
-        {
-          to_msgpack_internal(el, v);
-        }
-        break;
-      }
+    @complexity Constant.
 
-      case value_t::object:
-      {
-        const auto N = j.m_value.object->size();
-        if (N <= 15)
-        {
-          // fixmap
-          v.push_back(static_cast<uint8_t>(0x80 | (N & 0xf)));
-        }
-        else if (N <= 65535)
-        {
-          // map 16
-          v.push_back(0xde);
-          add_to_vector(v, 2, N);
-        }
-        else if (N <= 4294967295)
-        {
-          // map 32
-          v.push_back(0xdf);
-          add_to_vector(v, 4, N);
-        }
+    @requirement This function helps `basic_json` satisfying the
+    [Container](http://en.cppreference.com/w/cpp/concept/Container)
+    requirements:
+    - The complexity is constant.
+    - Has the semantics of `const_cast<const basic_json&>(*this).end()`.
 
-        // append each element
-        for (const auto &el : *j.m_value.object)
-        {
-          to_msgpack_internal(el.first, v);
-          to_msgpack_internal(el.second, v);
-        }
-        break;
-      }
+    @liveexample{The following code shows an example for `cend()`.,cend}
+
+    @sa @ref end() -- returns an iterator to the end
+    @sa @ref begin() -- returns an iterator to the beginning
+    @sa @ref cbegin() -- returns a const iterator to the beginning
 
-      default:
-      {
-        break;
-      }
+    @since version 1.0.0
+    */
+    const_iterator cend() const noexcept
+    {
+        const_iterator result(this);
+        result.set_end();
+        return result;
     }
-  }
 
-  /*!
-  @brief create a CBOR serialization of a given JSON value
+    /*!
+    @brief returns an iterator to the reverse-beginning
 
-  This is a straightforward implementation of the CBOR specification.
+    Returns an iterator to the reverse-beginning; that is, the last element.
 
-  @param[in] j  JSON value to serialize
-  @param[in,out] v  byte vector to write the serialization to
+    @image html range-rbegin-rend.svg "Illustration from cppreference.com"
 
-  @sa https://tools.ietf.org/html/rfc7049
-  */
-  static void to_cbor_internal(const basic_json &j, std::vector<uint8_t> &v)
-  {
-    switch (j.type())
-    {
-      case value_t::null:
-      {
-        v.push_back(0xf6);
-        break;
-      }
+    @complexity Constant.
 
-      case value_t::boolean:
-      {
-        v.push_back(j.m_value.boolean ? 0xf5 : 0xf4);
-        break;
-      }
-
-      case value_t::number_integer:
-      {
-        if (j.m_value.number_integer >= 0)
-        {
-          // CBOR does not differentiate between positive signed
-          // integers and unsigned integers. Therefore, we used the
-          // code from the value_t::number_unsigned case here.
-          if (j.m_value.number_integer <= 0x17)
-          {
-            add_to_vector(v, 1, j.m_value.number_integer);
-          }
-          else if (j.m_value.number_integer <= UINT8_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)
-          {
-            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)
-          {
-            v.push_back(0x1a);
-            // four-byte uint32_t
-            add_to_vector(v, 4, j.m_value.number_integer);
-          }
-          else
-          {
-            v.push_back(0x1b);
-            // eight-byte uint64_t
-            add_to_vector(v, 8, j.m_value.number_integer);
-          }
-        }
-        else
-        {
-          // The conversions below encode the sign in the first
-          // byte, and the value is converted to a positive number.
-          const auto positive_number = -1 - j.m_value.number_integer;
-          if (j.m_value.number_integer >= -24)
-          {
-            v.push_back(static_cast<uint8_t>(0x20 + positive_number));
-          }
-          else if (positive_number <= UINT8_MAX)
-          {
-            // int 8
-            v.push_back(0x38);
-            add_to_vector(v, 1, positive_number);
-          }
-          else if (positive_number <= UINT16_MAX)
-          {
-            // int 16
-            v.push_back(0x39);
-            add_to_vector(v, 2, positive_number);
-          }
-          else if (positive_number <= UINT32_MAX)
-          {
-            // int 32
-            v.push_back(0x3a);
-            add_to_vector(v, 4, positive_number);
-          }
-          else
-          {
-            // int 64
-            v.push_back(0x3b);
-            add_to_vector(v, 8, positive_number);
-          }
-        }
-        break;
-      }
+    @requirement This function helps `basic_json` satisfying the
+    [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer)
+    requirements:
+    - The complexity is constant.
+    - Has the semantics of `reverse_iterator(end())`.
 
-      case value_t::number_unsigned:
-      {
-        if (j.m_value.number_unsigned <= 0x17)
-        {
-          v.push_back(static_cast<uint8_t>(j.m_value.number_unsigned));
-        }
-        else if (j.m_value.number_unsigned <= 0xff)
-        {
-          v.push_back(0x18);
-          // one-byte uint8_t
-          add_to_vector(v, 1, j.m_value.number_unsigned);
-        }
-        else if (j.m_value.number_unsigned <= 0xffff)
-        {
-          v.push_back(0x19);
-          // two-byte uint16_t
-          add_to_vector(v, 2, j.m_value.number_unsigned);
-        }
-        else if (j.m_value.number_unsigned <= 0xffffffff)
-        {
-          v.push_back(0x1a);
-          // four-byte uint32_t
-          add_to_vector(v, 4, j.m_value.number_unsigned);
-        }
-        else if (j.m_value.number_unsigned <= 0xffffffffffffffff)
-        {
-          v.push_back(0x1b);
-          // eight-byte uint64_t
-          add_to_vector(v, 8, j.m_value.number_unsigned);
-        }
-        break;
-      }
+    @liveexample{The following code shows an example for `rbegin()`.,rbegin}
 
-      case value_t::number_float:
-      {
-        // Double-Precision Float
-        v.push_back(0xfb);
-        const auto *helper =
-            reinterpret_cast<const uint8_t *>(&(j.m_value.number_float));
-        for (size_t i = 0; i < 8; ++i)
-        {
-          v.push_back(helper[7 - i]);
-        }
-        break;
-      }
+    @sa @ref crbegin() -- returns a const reverse iterator to the beginning
+    @sa @ref rend() -- returns a reverse iterator to the end
+    @sa @ref crend() -- returns a const reverse iterator to the end
 
-      case value_t::string:
-      {
-        const auto N = j.m_value.string->size();
-        if (N <= 0x17)
-        {
-          v.push_back(0x60 + N); // 1 byte for string + size
-        }
-        else if (N <= 0xff)
-        {
-          v.push_back(0x78); // one-byte uint8_t for N
-          add_to_vector(v, 1, N);
-        }
-        else if (N <= 0xffff)
-        {
-          v.push_back(0x79); // two-byte uint16_t for N
-          add_to_vector(v, 2, N);
-        }
-        else if (N <= 0xffffffff)
-        {
-          v.push_back(0x7a); // four-byte uint32_t for N
-          add_to_vector(v, 4, N);
-        }
-        // LCOV_EXCL_START
-        else if (N <= 0xffffffffffffffff)
-        {
-          v.push_back(0x7b); // eight-byte uint64_t for N
-          add_to_vector(v, 8, N);
-        }
-        // LCOV_EXCL_STOP
+    @since version 1.0.0
+    */
+    reverse_iterator rbegin() noexcept { return reverse_iterator(end()); }
 
-        // append string
-        std::copy(j.m_value.string->begin(), j.m_value.string->end(),
-                  std::back_inserter(v));
-        break;
-      }
+    /*!
+    @copydoc basic_json::crbegin()
+    */
+    const_reverse_iterator rbegin() const noexcept { return crbegin(); }
 
-      case value_t::array:
-      {
-        const auto N = j.m_value.array->size();
-        if (N <= 0x17)
-        {
-          v.push_back(0x80 + N); // 1 byte for array + size
-        }
-        else if (N <= 0xff)
-        {
-          v.push_back(0x98); // one-byte uint8_t for N
-          add_to_vector(v, 1, N);
-        }
-        else if (N <= 0xffff)
-        {
-          v.push_back(0x99); // two-byte uint16_t for N
-          add_to_vector(v, 2, N);
-        }
-        else if (N <= 0xffffffff)
-        {
-          v.push_back(0x9a); // four-byte uint32_t for N
-          add_to_vector(v, 4, N);
-        }
-        // LCOV_EXCL_START
-        else if (N <= 0xffffffffffffffff)
-        {
-          v.push_back(0x9b); // eight-byte uint64_t for N
-          add_to_vector(v, 8, N);
-        }
-        // LCOV_EXCL_STOP
+    /*!
+    @brief returns an iterator to the reverse-end
 
-        // append each element
-        for (const auto &el : *j.m_value.array)
-        {
-          to_cbor_internal(el, v);
-        }
-        break;
-      }
+    Returns an iterator to the reverse-end; that is, one before the first
+    element.
 
-      case value_t::object:
-      {
-        const auto N = j.m_value.object->size();
-        if (N <= 0x17)
-        {
-          v.push_back(0xa0 + N); // 1 byte for object + size
-        }
-        else if (N <= 0xff)
-        {
-          v.push_back(0xb8);
-          add_to_vector(v, 1, N); // one-byte uint8_t for N
-        }
-        else if (N <= 0xffff)
-        {
-          v.push_back(0xb9);
-          add_to_vector(v, 2, N); // two-byte uint16_t for N
-        }
-        else if (N <= 0xffffffff)
-        {
-          v.push_back(0xba);
-          add_to_vector(v, 4, N); // four-byte uint32_t for N
-        }
-        // LCOV_EXCL_START
-        else if (N <= 0xffffffffffffffff)
-        {
-          v.push_back(0xbb);
-          add_to_vector(v, 8, N); // eight-byte uint64_t for N
-        }
-        // LCOV_EXCL_STOP
+    @image html range-rbegin-rend.svg "Illustration from cppreference.com"
 
-        // append each element
-        for (const auto &el : *j.m_value.object)
-        {
-          to_cbor_internal(el.first, v);
-          to_cbor_internal(el.second, v);
-        }
-        break;
-      }
+    @complexity Constant.
 
-      default:
-      {
-        break;
-      }
-    }
-  }
+    @requirement This function helps `basic_json` satisfying the
+    [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer)
+    requirements:
+    - The complexity is constant.
+    - Has the semantics of `reverse_iterator(begin())`.
+
+    @liveexample{The following code shows an example for `rend()`.,rend}
+
+    @sa @ref crend() -- returns a const reverse iterator to the end
+    @sa @ref rbegin() -- returns a reverse iterator to the beginning
+    @sa @ref crbegin() -- returns a const reverse iterator to the beginning
+
+    @since version 1.0.0
+    */
+    reverse_iterator rend() noexcept { return reverse_iterator(begin()); }
 
-  /*
-  @brief checks if given lengths do not exceed the size of a given vector
+    /*!
+    @copydoc basic_json::crend()
+    */
+    const_reverse_iterator rend() const noexcept { return crend(); }
 
-  To secure the access to the byte vector during CBOR/MessagePack
-  deserialization, bytes are copied from the vector into buffers. This
-  function checks if the number of bytes to copy (@a len) does not exceed
-  the size @s size of the vector. Additionally, an @a offset is given from
-  where to start reading the bytes.
+    /*!
+    @brief returns a const reverse iterator to the last element
 
-  This function checks whether reading the bytes is safe; that is, offset is
-  a valid index in the vector, offset+len
+    Returns a const iterator to the reverse-beginning; that is, the last
+    element.
 
-  @param[in] size    size of the byte vector
-  @param[in] len     number of bytes to read
-  @param[in] offset  offset where to start reading
+    @image html range-rbegin-rend.svg "Illustration from cppreference.com"
 
-  vec:  x x x x x X X X X X
-        ^         ^         ^
-        0         offset    len
+    @complexity Constant.
 
-  @throws out_of_range if `len > v.size()`
-  */
-  static void check_length(const size_t size, const size_t len,
-                           const size_t offset)
-  {
-    // 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"));
-    }
+    @requirement This function helps `basic_json` satisfying the
+    [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer)
+    requirements:
+    - The complexity is constant.
+    - Has the semantics of `const_cast<const basic_json&>(*this).rbegin()`.
 
-    // second case: adding offset would result in overflow
-    if ((size > (std::numeric_limits<size_t>::max() - offset)))
-    {
-      JSON_THROW(std::out_of_range("len+offset out of range"));
-    }
+    @liveexample{The following code shows an example for `crbegin()`.,crbegin}
 
-    // last case: reading past the end of the vector
-    if (len + offset > size)
+    @sa @ref rbegin() -- returns a reverse iterator to the beginning
+    @sa @ref rend() -- returns a reverse iterator to the end
+    @sa @ref crend() -- returns a const reverse iterator to the end
+
+    @since version 1.0.0
+    */
+    const_reverse_iterator crbegin() const noexcept
     {
-      JSON_THROW(std::out_of_range("len+offset out of range"));
+        return const_reverse_iterator(cend());
     }
-  }
 
-  /*!
-  @brief create a JSON value from a given MessagePack vector
+    /*!
+    @brief returns a const reverse iterator to one before the first
+
+    Returns a const reverse iterator to the reverse-end; that is, one before
+    the first element.
 
-  @param[in] v  MessagePack serialization
-  @param[in] idx  byte index to start reading from @a v
+    @image html range-rbegin-rend.svg "Illustration from cppreference.com"
 
-  @return deserialized JSON value
+    @complexity Constant.
 
-  @throw std::invalid_argument 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
+    @requirement This function helps `basic_json` satisfying the
+    [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer)
+    requirements:
+    - The complexity is constant.
+    - Has the semantics of `const_cast<const basic_json&>(*this).rend()`.
 
-  @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);
+    @liveexample{The following code shows an example for `crend()`.,crend}
 
-    // store and increment index
-    const size_t current_idx = idx++;
+    @sa @ref rend() -- returns a reverse iterator to the end
+    @sa @ref rbegin() -- returns a reverse iterator to the beginning
+    @sa @ref crbegin() -- returns a const reverse iterator to the beginning
 
-    if (v[current_idx] <= 0xbf)
+    @since version 1.0.0
+    */
+    const_reverse_iterator crend() const noexcept
     {
-      if (v[current_idx] <= 0x7f) // positive fixint
-      {
-        return v[current_idx];
-      }
-      if (v[current_idx] <= 0x8f) // fixmap
-      {
-        basic_json result = value_t::object;
-        const size_t len = v[current_idx] & 0x0f;
-        for (size_t i = 0; i < len; ++i)
-        {
-          std::string key = from_msgpack_internal(v, idx);
-          result[key] = from_msgpack_internal(v, idx);
-        }
-        return result;
-      }
-      else if (v[current_idx] <= 0x9f) // fixarray
-      {
-        basic_json result = value_t::array;
-        const size_t len = v[current_idx] & 0x0f;
-        for (size_t i = 0; i < len; ++i)
-        {
-          result.push_back(from_msgpack_internal(v, idx));
-        }
-        return result;
-      }
-      else // fixstr
-      {
-        const size_t len = v[current_idx] & 0x1f;
-        const size_t offset = current_idx + 1;
-        idx += len; // skip content bytes
-        check_length(v.size(), len, offset);
-        return std::string(reinterpret_cast<const char *>(v.data()) + offset,
-                           len);
-      }
+        return const_reverse_iterator(cbegin());
     }
-    else if (v[current_idx] >= 0xe0) // negative fixint
+
+private:
+    // forward declaration
+    template <typename IteratorType>
+    class iteration_proxy;
+
+public:
+    /*!
+    @brief wrapper to access iterator member functions in range-based for
+
+    This function allows to access @ref iterator::key() and @ref
+    iterator::value() during range-based for loops. In these loops, a
+    reference to the JSON values is returned, so there is no access to the
+    underlying iterator.
+
+    @note The name of this function is not yet final and may change in the
+    future.
+    */
+    static iteration_proxy<iterator> iterator_wrapper(reference cont)
     {
-      return static_cast<int8_t>(v[current_idx]);
+        return iteration_proxy<iterator>(cont);
     }
-    else
+
+    /*!
+    @copydoc iterator_wrapper(reference)
+    */
+    static iteration_proxy<const_iterator>
+    iterator_wrapper(const_reference cont)
     {
-      switch (v[current_idx])
-      {
-        case 0xc0: // nil
-        {
-          return value_t::null;
-        }
+        return iteration_proxy<const_iterator>(cont);
+    }
 
-        case 0xc2: // false
-        {
-          return false;
-        }
+    /// @}
 
-        case 0xc3: // true
-        {
-          return true;
-        }
+    //////////////
+    // capacity //
+    //////////////
 
-        case 0xca: // float 32
-        {
-          // copy bytes in reverse order into the double variable
-          float res;
-          for (size_t byte = 0; byte < sizeof(float); ++byte)
-          {
-            reinterpret_cast<uint8_t *>(&res)[sizeof(float) - byte - 1] =
-                v.at(current_idx + 1 + byte);
-          }
-          idx += sizeof(float); // skip content bytes
-          return res;
-        }
+    /// @name capacity
+    /// @{
 
-        case 0xcb: // float 64
-        {
-          // copy bytes in reverse order into the double variable
-          double res;
-          for (size_t byte = 0; byte < sizeof(double); ++byte)
-          {
-            reinterpret_cast<uint8_t *>(&res)[sizeof(double) - byte - 1] =
-                v.at(current_idx + 1 + byte);
-          }
-          idx += sizeof(double); // skip content bytes
-          return res;
-        }
+    /*!
+    @brief checks whether the container is empty
 
-        case 0xcc: // uint 8
-        {
-          idx += 1; // skip content byte
-          return get_from_vector<uint8_t>(v, current_idx);
-        }
+    Checks if a JSON value has no elements.
 
-        case 0xcd: // uint 16
-        {
-          idx += 2; // skip 2 content bytes
-          return get_from_vector<uint16_t>(v, current_idx);
-        }
+    @return The return value depends on the different types and is
+            defined as follows:
+            Value type  | return value
+            ----------- | -------------
+            null        | `true`
+            boolean     | `false`
+            string      | `false`
+            number      | `false`
+            object      | result of function `object_t::empty()`
+            array       | result of function `array_t::empty()`
 
-        case 0xce: // uint 32
-        {
-          idx += 4; // skip 4 content bytes
-          return get_from_vector<uint32_t>(v, current_idx);
-        }
+    @note This function does not return whether a string stored as JSON value
+    is empty - it returns whether the JSON container itself is empty which is
+    false in the case of a string.
 
-        case 0xcf: // uint 64
-        {
-          idx += 8; // skip 8 content bytes
-          return get_from_vector<uint64_t>(v, current_idx);
-        }
+    @complexity Constant, as long as @ref array_t and @ref object_t satisfy
+    the Container concept; that is, their `empty()` functions have constant
+    complexity.
 
-        case 0xd0: // int 8
-        {
-          idx += 1; // skip content byte
-          return get_from_vector<int8_t>(v, current_idx);
-        }
+    @requirement This function helps `basic_json` satisfying the
+    [Container](http://en.cppreference.com/w/cpp/concept/Container)
+    requirements:
+    - The complexity is constant.
+    - Has the semantics of `begin() == end()`.
 
-        case 0xd1: // int 16
-        {
-          idx += 2; // skip 2 content bytes
-          return get_from_vector<int16_t>(v, current_idx);
-        }
+    @liveexample{The following code uses `empty()` to check if a JSON
+    object contains any elements.,empty}
 
-        case 0xd2: // int 32
-        {
-          idx += 4; // skip 4 content bytes
-          return get_from_vector<int32_t>(v, current_idx);
-        }
+    @sa @ref size() -- returns the number of elements
 
-        case 0xd3: // int 64
+    @since version 1.0.0
+    */
+    bool empty() const noexcept
+    {
+        switch (m_type)
         {
-          idx += 8; // skip 8 content bytes
-          return get_from_vector<int64_t>(v, current_idx);
-        }
-
-        case 0xd9: // str 8
+        case value_t::null:
         {
-          const auto len =
-              static_cast<size_t>(get_from_vector<uint8_t>(v, current_idx));
-          const size_t offset = current_idx + 2;
-          idx += len + 1; // skip size byte + content bytes
-          check_length(v.size(), len, offset);
-          return std::string(reinterpret_cast<const char *>(v.data()) + offset,
-                             len);
+            // null values are empty
+            return true;
         }
 
-        case 0xda: // str 16
+        case value_t::array:
         {
-          const auto len =
-              static_cast<size_t>(get_from_vector<uint16_t>(v, current_idx));
-          const size_t offset = current_idx + 3;
-          idx += len + 2; // skip 2 size bytes + content bytes
-          check_length(v.size(), len, offset);
-          return std::string(reinterpret_cast<const char *>(v.data()) + offset,
-                             len);
+            // delegate call to array_t::empty()
+            return m_value.array->empty();
         }
 
-        case 0xdb: // str 32
+        case value_t::object:
         {
-          const auto len =
-              static_cast<size_t>(get_from_vector<uint32_t>(v, current_idx));
-          const size_t offset = current_idx + 5;
-          idx += len + 4; // skip 4 size bytes + content bytes
-          check_length(v.size(), len, offset);
-          return std::string(reinterpret_cast<const char *>(v.data()) + offset,
-                             len);
+            // delegate call to object_t::empty()
+            return m_value.object->empty();
         }
 
-        case 0xdc: // array 16
+        default:
         {
-          basic_json result = value_t::array;
-          const auto len =
-              static_cast<size_t>(get_from_vector<uint16_t>(v, current_idx));
-          idx += 2; // skip 2 size bytes
-          for (size_t i = 0; i < len; ++i)
-          {
-            result.push_back(from_msgpack_internal(v, idx));
-          }
-          return result;
+            // all other types are nonempty
+            return false;
+        }
         }
+    }
 
-        case 0xdd: // array 32
+    /*!
+    @brief returns the number of elements
+
+    Returns the number of elements in a JSON value.
+
+    @return The return value depends on the different types and is
+            defined as follows:
+            Value type  | return value
+            ----------- | -------------
+            null        | `0`
+            boolean     | `1`
+            string      | `1`
+            number      | `1`
+            object      | result of function object_t::size()
+            array       | result of function array_t::size()
+
+    @note This function does not return the length of a string stored as JSON
+    value - it returns the number of elements in the JSON value which is 1 in
+    the case of a string.
+
+    @complexity Constant, as long as @ref array_t and @ref object_t satisfy
+    the Container concept; that is, their size() functions have constant
+    complexity.
+
+    @requirement This function helps `basic_json` satisfying the
+    [Container](http://en.cppreference.com/w/cpp/concept/Container)
+    requirements:
+    - The complexity is constant.
+    - Has the semantics of `std::distance(begin(), end())`.
+
+    @liveexample{The following code calls `size()` on the different value
+    types.,size}
+
+    @sa @ref empty() -- checks whether the container is empty
+    @sa @ref max_size() -- returns the maximal number of elements
+
+    @since version 1.0.0
+    */
+    size_type size() const noexcept
+    {
+        switch (m_type)
+        {
+        case value_t::null:
         {
-          basic_json result = value_t::array;
-          const auto len =
-              static_cast<size_t>(get_from_vector<uint32_t>(v, current_idx));
-          idx += 4; // skip 4 size bytes
-          for (size_t i = 0; i < len; ++i)
-          {
-            result.push_back(from_msgpack_internal(v, idx));
-          }
-          return result;
+            // null values are empty
+            return 0;
         }
 
-        case 0xde: // map 16
+        case value_t::array:
         {
-          basic_json result = value_t::object;
-          const auto len =
-              static_cast<size_t>(get_from_vector<uint16_t>(v, current_idx));
-          idx += 2; // skip 2 size bytes
-          for (size_t i = 0; i < len; ++i)
-          {
-            std::string key = from_msgpack_internal(v, idx);
-            result[key] = from_msgpack_internal(v, idx);
-          }
-          return result;
+            // delegate call to array_t::size()
+            return m_value.array->size();
         }
 
-        case 0xdf: // map 32
+        case value_t::object:
         {
-          basic_json result = value_t::object;
-          const auto len =
-              static_cast<size_t>(get_from_vector<uint32_t>(v, current_idx));
-          idx += 4; // skip 4 size bytes
-          for (size_t i = 0; i < len; ++i)
-          {
-            std::string key = from_msgpack_internal(v, idx);
-            result[key] = from_msgpack_internal(v, idx);
-          }
-          return result;
+            // delegate call to object_t::size()
+            return m_value.object->size();
         }
 
         default:
         {
-          JSON_THROW(std::invalid_argument(
-              "error parsing a msgpack @ " + std::to_string(current_idx) +
-              ": " + std::to_string(static_cast<int>(v[current_idx]))));
-        }
-      }
-    }
-  }
-
-  /*!
-  @brief create a JSON value from a given CBOR vector
-
-  @param[in] v  CBOR serialization
-  @param[in] idx  byte index to start reading from @a v
-
-  @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
-
-  @sa https://tools.ietf.org/html/rfc7049
-  */
-  static basic_json from_cbor_internal(const std::vector<uint8_t> &v,
-                                       size_t &idx)
-  {
-    // store and increment index
-    const size_t current_idx = idx++;
-
-    switch (v.at(current_idx))
-    {
-      // Integer 0x00..0x17 (0..23)
-      case 0x00:
-      case 0x01:
-      case 0x02:
-      case 0x03:
-      case 0x04:
-      case 0x05:
-      case 0x06:
-      case 0x07:
-      case 0x08:
-      case 0x09:
-      case 0x0a:
-      case 0x0b:
-      case 0x0c:
-      case 0x0d:
-      case 0x0e:
-      case 0x0f:
-      case 0x10:
-      case 0x11:
-      case 0x12:
-      case 0x13:
-      case 0x14:
-      case 0x15:
-      case 0x16:
-      case 0x17:
-      {
-        return v[current_idx];
-      }
-
-      case 0x18: // Unsigned integer (one-byte uint8_t follows)
-      {
-        idx += 1; // skip content byte
-        return get_from_vector<uint8_t>(v, current_idx);
-      }
-
-      case 0x19: // Unsigned integer (two-byte uint16_t follows)
-      {
-        idx += 2; // skip 2 content bytes
-        return get_from_vector<uint16_t>(v, current_idx);
-      }
-
-      case 0x1a: // Unsigned integer (four-byte uint32_t follows)
-      {
-        idx += 4; // skip 4 content bytes
-        return get_from_vector<uint32_t>(v, current_idx);
-      }
-
-      case 0x1b: // Unsigned integer (eight-byte uint64_t follows)
-      {
-        idx += 8; // skip 8 content bytes
-        return get_from_vector<uint64_t>(v, current_idx);
-      }
-
-      // Negative integer -1-0x00..-1-0x17 (-1..-24)
-      case 0x20:
-      case 0x21:
-      case 0x22:
-      case 0x23:
-      case 0x24:
-      case 0x25:
-      case 0x26:
-      case 0x27:
-      case 0x28:
-      case 0x29:
-      case 0x2a:
-      case 0x2b:
-      case 0x2c:
-      case 0x2d:
-      case 0x2e:
-      case 0x2f:
-      case 0x30:
-      case 0x31:
-      case 0x32:
-      case 0x33:
-      case 0x34:
-      case 0x35:
-      case 0x36:
-      case 0x37:
-      {
-        return static_cast<int8_t>(0x20 - 1 - v[current_idx]);
-      }
-
-      case 0x38: // Negative integer (one-byte uint8_t follows)
-      {
-        idx += 1; // skip content byte
-        // must be uint8_t !
-        return static_cast<number_integer_t>(-1) -
-               get_from_vector<uint8_t>(v, current_idx);
-      }
-
-      case 0x39: // Negative integer -1-n (two-byte uint16_t follows)
-      {
-        idx += 2; // skip 2 content bytes
-        return static_cast<number_integer_t>(-1) -
-               get_from_vector<uint16_t>(v, current_idx);
-      }
-
-      case 0x3a: // Negative integer -1-n (four-byte uint32_t follows)
-      {
-        idx += 4; // skip 4 content bytes
-        return static_cast<number_integer_t>(-1) -
-               get_from_vector<uint32_t>(v, current_idx);
-      }
-
-      case 0x3b: // Negative integer -1-n (eight-byte uint64_t follows)
-      {
-        idx += 8; // skip 8 content bytes
-        return static_cast<number_integer_t>(-1) -
-               static_cast<number_integer_t>(
-                   get_from_vector<uint64_t>(v, current_idx));
-      }
-
-      // UTF-8 string (0x00..0x17 bytes follow)
-      case 0x60:
-      case 0x61:
-      case 0x62:
-      case 0x63:
-      case 0x64:
-      case 0x65:
-      case 0x66:
-      case 0x67:
-      case 0x68:
-      case 0x69:
-      case 0x6a:
-      case 0x6b:
-      case 0x6c:
-      case 0x6d:
-      case 0x6e:
-      case 0x6f:
-      case 0x70:
-      case 0x71:
-      case 0x72:
-      case 0x73:
-      case 0x74:
-      case 0x75:
-      case 0x76:
-      case 0x77:
-      {
-        const auto len = static_cast<size_t>(v[current_idx] - 0x60);
-        const size_t offset = current_idx + 1;
-        idx += len; // skip content bytes
-        check_length(v.size(), len, offset);
-        return std::string(reinterpret_cast<const char *>(v.data()) + offset,
-                           len);
-      }
-
-      case 0x78: // UTF-8 string (one-byte uint8_t for n follows)
-      {
-        const auto len =
-            static_cast<size_t>(get_from_vector<uint8_t>(v, current_idx));
-        const size_t offset = current_idx + 2;
-        idx += len + 1; // skip size byte + content bytes
-        check_length(v.size(), len, offset);
-        return std::string(reinterpret_cast<const char *>(v.data()) + offset,
-                           len);
-      }
-
-      case 0x79: // UTF-8 string (two-byte uint16_t for n follow)
-      {
-        const auto len =
-            static_cast<size_t>(get_from_vector<uint16_t>(v, current_idx));
-        const size_t offset = current_idx + 3;
-        idx += len + 2; // skip 2 size bytes + content bytes
-        check_length(v.size(), len, offset);
-        return std::string(reinterpret_cast<const char *>(v.data()) + offset,
-                           len);
-      }
-
-      case 0x7a: // UTF-8 string (four-byte uint32_t for n follow)
-      {
-        const auto len =
-            static_cast<size_t>(get_from_vector<uint32_t>(v, current_idx));
-        const size_t offset = current_idx + 5;
-        idx += len + 4; // skip 4 size bytes + content bytes
-        check_length(v.size(), len, offset);
-        return std::string(reinterpret_cast<const char *>(v.data()) + offset,
-                           len);
-      }
-
-      case 0x7b: // UTF-8 string (eight-byte uint64_t for n follow)
-      {
-        const auto len =
-            static_cast<size_t>(get_from_vector<uint64_t>(v, current_idx));
-        const size_t offset = current_idx + 9;
-        idx += len + 8; // skip 8 size bytes + content bytes
-        check_length(v.size(), len, offset);
-        return std::string(reinterpret_cast<const char *>(v.data()) + offset,
-                           len);
-      }
-
-      case 0x7f: // UTF-8 string (indefinite length)
-      {
-        std::string result;
-        while (v.at(idx) != 0xff)
-        {
-          string_t s = from_cbor_internal(v, idx);
-          result += s;
-        }
-        // skip break byte (0xFF)
-        idx += 1;
-        return result;
-      }
-
-      // array (0x00..0x17 data items follow)
-      case 0x80:
-      case 0x81:
-      case 0x82:
-      case 0x83:
-      case 0x84:
-      case 0x85:
-      case 0x86:
-      case 0x87:
-      case 0x88:
-      case 0x89:
-      case 0x8a:
-      case 0x8b:
-      case 0x8c:
-      case 0x8d:
-      case 0x8e:
-      case 0x8f:
-      case 0x90:
-      case 0x91:
-      case 0x92:
-      case 0x93:
-      case 0x94:
-      case 0x95:
-      case 0x96:
-      case 0x97:
-      {
-        basic_json result = value_t::array;
-        const auto len = static_cast<size_t>(v[current_idx] - 0x80);
-        for (size_t i = 0; i < len; ++i)
-        {
-          result.push_back(from_cbor_internal(v, idx));
+            // all other types have size 1
+            return 1;
         }
-        return result;
-      }
-
-      case 0x98: // array (one-byte uint8_t for n follows)
-      {
-        basic_json result = value_t::array;
-        const auto len =
-            static_cast<size_t>(get_from_vector<uint8_t>(v, current_idx));
-        idx += 1; // skip 1 size byte
-        for (size_t i = 0; i < len; ++i)
-        {
-          result.push_back(from_cbor_internal(v, idx));
         }
-        return result;
-      }
+    }
 
-      case 0x99: // array (two-byte uint16_t for n follow)
-      {
-        basic_json result = value_t::array;
-        const auto len =
-            static_cast<size_t>(get_from_vector<uint16_t>(v, current_idx));
-        idx += 2; // skip 4 size bytes
-        for (size_t i = 0; i < len; ++i)
+    /*!
+    @brief returns the maximum possible number of elements
+
+    Returns the maximum number of elements a JSON value is able to hold due to
+    system or library implementation limitations, i.e. `std::distance(begin(),
+    end())` for the JSON value.
+
+    @return The return value depends on the different types and is
+            defined as follows:
+            Value type  | return value
+            ----------- | -------------
+            null        | `0` (same as `size()`)
+            boolean     | `1` (same as `size()`)
+            string      | `1` (same as `size()`)
+            number      | `1` (same as `size()`)
+            object      | result of function `object_t::max_size()`
+            array       | result of function `array_t::max_size()`
+
+    @complexity Constant, as long as @ref array_t and @ref object_t satisfy
+    the Container concept; that is, their `max_size()` functions have constant
+    complexity.
+
+    @requirement This function helps `basic_json` satisfying the
+    [Container](http://en.cppreference.com/w/cpp/concept/Container)
+    requirements:
+    - The complexity is constant.
+    - Has the semantics of returning `b.size()` where `b` is the largest
+      possible JSON value.
+
+    @liveexample{The following code calls `max_size()` on the different value
+    types. Note the output is implementation specific.,max_size}
+
+    @sa @ref size() -- returns the number of elements
+
+    @since version 1.0.0
+    */
+    size_type max_size() const noexcept
+    {
+        switch (m_type)
         {
-          result.push_back(from_cbor_internal(v, idx));
-        }
-        return result;
-      }
-
-      case 0x9a: // array (four-byte uint32_t for n follow)
-      {
-        basic_json result = value_t::array;
-        const auto len =
-            static_cast<size_t>(get_from_vector<uint32_t>(v, current_idx));
-        idx += 4; // skip 4 size bytes
-        for (size_t i = 0; i < len; ++i)
+        case value_t::array:
         {
-          result.push_back(from_cbor_internal(v, idx));
+            // delegate call to array_t::max_size()
+            return m_value.array->max_size();
         }
-        return result;
-      }
 
-      case 0x9b: // array (eight-byte uint64_t for n follow)
-      {
-        basic_json result = value_t::array;
-        const auto len =
-            static_cast<size_t>(get_from_vector<uint64_t>(v, current_idx));
-        idx += 8; // skip 8 size bytes
-        for (size_t i = 0; i < len; ++i)
+        case value_t::object:
         {
-          result.push_back(from_cbor_internal(v, idx));
+            // delegate call to object_t::max_size()
+            return m_value.object->max_size();
         }
-        return result;
-      }
 
-      case 0x9f: // array (indefinite length)
-      {
-        basic_json result = value_t::array;
-        while (v.at(idx) != 0xff)
+        default:
         {
-          result.push_back(from_cbor_internal(v, idx));
+            // all other types have max_size() == size()
+            return size();
         }
-        // skip break byte (0xFF)
-        idx += 1;
-        return result;
-      }
-
-      // map (0x00..0x17 pairs of data items follow)
-      case 0xa0:
-      case 0xa1:
-      case 0xa2:
-      case 0xa3:
-      case 0xa4:
-      case 0xa5:
-      case 0xa6:
-      case 0xa7:
-      case 0xa8:
-      case 0xa9:
-      case 0xaa:
-      case 0xab:
-      case 0xac:
-      case 0xad:
-      case 0xae:
-      case 0xaf:
-      case 0xb0:
-      case 0xb1:
-      case 0xb2:
-      case 0xb3:
-      case 0xb4:
-      case 0xb5:
-      case 0xb6:
-      case 0xb7:
-      {
-        basic_json result = value_t::object;
-        const auto len = static_cast<size_t>(v[current_idx] - 0xa0);
-        for (size_t i = 0; i < len; ++i)
-        {
-          std::string key = from_cbor_internal(v, idx);
-          result[key] = from_cbor_internal(v, idx);
         }
-        return result;
-      }
+    }
 
-      case 0xb8: // map (one-byte uint8_t for n follows)
-      {
-        basic_json result = value_t::object;
-        const auto len =
-            static_cast<size_t>(get_from_vector<uint8_t>(v, current_idx));
-        idx += 1; // skip 1 size byte
-        for (size_t i = 0; i < len; ++i)
-        {
-          std::string key = from_cbor_internal(v, idx);
-          result[key] = from_cbor_internal(v, idx);
-        }
-        return result;
-      }
+    /// @}
 
-      case 0xb9: // map (two-byte uint16_t for n follow)
-      {
-        basic_json result = value_t::object;
-        const auto len =
-            static_cast<size_t>(get_from_vector<uint16_t>(v, current_idx));
-        idx += 2; // skip 2 size bytes
-        for (size_t i = 0; i < len; ++i)
-        {
-          std::string key = from_cbor_internal(v, idx);
-          result[key] = from_cbor_internal(v, idx);
-        }
-        return result;
-      }
+    ///////////////
+    // modifiers //
+    ///////////////
+
+    /// @name modifiers
+    /// @{
+
+    /*!
+    @brief clears the contents
+
+    Clears the content of a JSON value and resets it to the default value as
+    if @ref basic_json(value_t) would have been called:
 
-      case 0xba: // map (four-byte uint32_t for n follow)
-      {
-        basic_json result = value_t::object;
-        const auto len =
-            static_cast<size_t>(get_from_vector<uint32_t>(v, current_idx));
-        idx += 4; // skip 4 size bytes
-        for (size_t i = 0; i < len; ++i)
+    Value type  | initial value
+    ----------- | -------------
+    null        | `null`
+    boolean     | `false`
+    string      | `""`
+    number      | `0`
+    object      | `{}`
+    array       | `[]`
+
+    @complexity Linear in the size of the JSON value.
+
+    @liveexample{The example below shows the effect of `clear()` to different
+    JSON types.,clear}
+
+    @since version 1.0.0
+    */
+    void clear() noexcept
+    {
+        switch (m_type)
+        {
+        case value_t::number_integer:
         {
-          std::string key = from_cbor_internal(v, idx);
-          result[key] = from_cbor_internal(v, idx);
+            m_value.number_integer = 0;
+            break;
         }
-        return result;
-      }
 
-      case 0xbb: // map (eight-byte uint64_t for n follow)
-      {
-        basic_json result = value_t::object;
-        const auto len =
-            static_cast<size_t>(get_from_vector<uint64_t>(v, current_idx));
-        idx += 8; // skip 8 size bytes
-        for (size_t i = 0; i < len; ++i)
+        case value_t::number_unsigned:
         {
-          std::string key = from_cbor_internal(v, idx);
-          result[key] = from_cbor_internal(v, idx);
+            m_value.number_unsigned = 0;
+            break;
         }
-        return result;
-      }
 
-      case 0xbf: // map (indefinite length)
-      {
-        basic_json result = value_t::object;
-        while (v.at(idx) != 0xff)
+        case value_t::number_float:
         {
-          std::string key = from_cbor_internal(v, idx);
-          result[key] = from_cbor_internal(v, idx);
+            m_value.number_float = 0.0;
+            break;
         }
-        // skip break byte (0xFF)
-        idx += 1;
-        return result;
-      }
-
-      case 0xf4: // false
-      {
-        return false;
-      }
-
-      case 0xf5: // true
-      {
-        return true;
-      }
-
-      case 0xf6: // null
-      {
-        return value_t::null;
-      }
-
-      case 0xf9: // Half-Precision Float (two-byte IEEE 754)
-      {
-        idx += 2; // skip two content bytes
 
-        // code from RFC 7049, Appendix D, Figure 3:
-        // As half-precision floating-point numbers were only added to
-        // IEEE 754 in 2008, today's programming platforms often still
-        // only have limited support for them. It is very easy to
-        // 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);
-        const int exp = (half >> 10) & 0x1f;
-        const int mant = half & 0x3ff;
-        double val;
-        if (exp == 0)
+        case value_t::boolean:
         {
-          val = std::ldexp(mant, -24);
+            m_value.boolean = false;
+            break;
         }
-        else if (exp != 31)
+
+        case value_t::string:
         {
-          val = std::ldexp(mant + 1024, exp - 25);
+            m_value.string->clear();
+            break;
         }
-        else
+
+        case value_t::array:
         {
-          val = mant == 0 ? INFINITY : NAN;
+            m_value.array->clear();
+            break;
         }
-        return (half & 0x8000) != 0 ? -val : val;
-      }
 
-      case 0xfa: // Single-Precision Float (four-byte IEEE 754)
-      {
-        // copy bytes in reverse order into the float variable
-        float res;
-        for (size_t byte = 0; byte < sizeof(float); ++byte)
+        case value_t::object:
         {
-          reinterpret_cast<uint8_t *>(&res)[sizeof(float) - byte - 1] =
-              v.at(current_idx + 1 + byte);
+            m_value.object->clear();
+            break;
         }
-        idx += sizeof(float); // skip content bytes
-        return res;
-      }
 
-      case 0xfb: // Double-Precision Float (eight-byte IEEE 754)
-      {
-        // copy bytes in reverse order into the double variable
-        double res;
-        for (size_t byte = 0; byte < sizeof(double); ++byte)
+        default:
         {
-          reinterpret_cast<uint8_t *>(&res)[sizeof(double) - byte - 1] =
-              v.at(current_idx + 1 + byte);
+            break;
+        }
         }
-        idx += sizeof(double); // skip content bytes
-        return res;
-      }
-
-      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]))));
-      }
     }
-  }
-
-public:
-  /*!
-  @brief create a MessagePack serialization of a given JSON value
-
-  Serializes a given JSON value @a j to a byte vector using the MessagePack
-  serialization format. MessagePack is a binary serialization format which
-  aims to be more compact than JSON itself, yet more efficient to parse.
-
-  @param[in] j  JSON value to serialize
-  @return MessagePack serialization as byte vector
-
-  @complexity Linear in the size of the JSON value @a j.
-
-  @liveexample{The example shows the serialization of a JSON value to a byte
-  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 to_cbor(const basic_json& for the related CBOR format
-  */
-  static std::vector<uint8_t> to_msgpack(const basic_json &j)
-  {
-    std::vector<uint8_t> result;
-    to_msgpack_internal(j, result);
-    return result;
-  }
-
-  /*!
-  @brief create a JSON value from a byte vector in MessagePack format
-
-  Deserializes a given byte vector @a v to a JSON value using the MessagePack
-  serialization format.
-
-  @param[in] v  a byte vector in MessagePack format
-  @return deserialized JSON value
-
-  @throw std::invalid_argument 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
-
-  @complexity Linear in the size of the byte vector @a v.
-
-  @liveexample{The example shows the deserialization of a byte vector in
-  MessagePack format to a JSON value.,from_msgpack}
-
-  @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
-  */
-  static basic_json from_msgpack(const std::vector<uint8_t> &v)
-  {
-    size_t i = 0;
-    return from_msgpack_internal(v, i);
-  }
-
-  /*!
-  @brief create a MessagePack serialization of a given JSON value
-
-  Serializes a given JSON value @a j to a byte vector using the CBOR (Concise
-  Binary Object Representation) serialization format. CBOR is a binary
-  serialization format which aims to be more compact than JSON itself, yet
-  more efficient to parse.
-
-  @param[in] j  JSON value to serialize
-  @return MessagePack serialization as byte vector
-
-  @complexity Linear in the size of the JSON value @a j.
-
-  @liveexample{The example shows the serialization of a JSON value to a byte
-  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 to_msgpack(const basic_json& for the related MessagePack format
-  */
-  static std::vector<uint8_t> to_cbor(const basic_json &j)
-  {
-    std::vector<uint8_t> result;
-    to_cbor_internal(j, result);
-    return result;
-  }
-
-  /*!
-  @brief create a JSON value from a byte vector in CBOR format
-
-  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 add an object to an array
 
-  /*!
-  @brief escape a string
+    Appends the given element @a val to the end of the JSON value. If the
+    function is called on a JSON null value, an empty array is created before
+    appending @a val.
 
-  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] val the value to add to the JSON array
 
-  @param[in] s  the string to escape
-  @return  the escaped string
+    @throw std::domain_error when called on a type other than JSON array or
+    null; example: `"cannot use push_back() with number"`
 
-  @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;
-    }
+    @complexity Amortized constant.
 
-    // create a result string of necessary size
-    string_t result(s.size() + space, '\\');
-    std::size_t pos = 0;
+    @liveexample{The example shows how `push_back()` and `+=` can be used to
+    add elements to a JSON array. Note how the `null` value was silently
+    converted to a JSON array.,push_back}
 
-    for (const auto &c : s)
+    @since version 1.0.0
+    */
+    void push_back(basic_json &&val)
     {
-      switch (c)
-      {
-        // quotation mark (0x22)
-        case '"':
+        // push_back only works for null objects or arrays
+        if (not(is_null() or is_array()))
         {
-          result[pos + 1] = '"';
-          pos += 2;
-          break;
+            JSON_THROW(std::domain_error("cannot use push_back() with " +
+                                         type_name()));
         }
 
-        // reverse solidus (0x5c)
-        case '\\':
+        // transform null object into an array
+        if (is_null())
         {
-          // nothing to change
-          pos += 2;
-          break;
+            m_type = value_t::array;
+            m_value = value_t::array;
+            assert_invariant();
         }
 
-        // backspace (0x08)
-        case '\b':
-        {
-          result[pos + 1] = 'b';
-          pos += 2;
-          break;
-        }
+        // add element to array (move semantics)
+        m_value.array->push_back(std::move(val));
+        // invalidate object
+        val.m_type = value_t::null;
+    }
 
-        // formfeed (0x0c)
-        case '\f':
-        {
-          result[pos + 1] = 'f';
-          pos += 2;
-          break;
-        }
-
-        // newline (0x0a)
-        case '\n':
-        {
-          result[pos + 1] = 'n';
-          pos += 2;
-          break;
-        }
+    /*!
+    @brief add an object to an array
+    @copydoc push_back(basic_json&&)
+    */
+    reference operator+=(basic_json &&val)
+    {
+        push_back(std::move(val));
+        return *this;
+    }
 
-        // carriage return (0x0d)
-        case '\r':
+    /*!
+    @brief add an object to an array
+    @copydoc push_back(basic_json&&)
+    */
+    void push_back(const basic_json &val)
+    {
+        // push_back only works for null objects or arrays
+        if (not(is_null() or is_array()))
         {
-          result[pos + 1] = 'r';
-          pos += 2;
-          break;
+            JSON_THROW(std::domain_error("cannot use push_back() with " +
+                                         type_name()));
         }
 
-        // horizontal tab (0x09)
-        case '\t':
+        // transform null object into an array
+        if (is_null())
         {
-          result[pos + 1] = 't';
-          pos += 2;
-          break;
+            m_type = value_t::array;
+            m_value = value_t::array;
+            assert_invariant();
         }
 
-        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;
-        }
-      }
+        // add element to array
+        m_value.array->push_back(val);
     }
 
-    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
+    /*!
+    @brief add an object to an array
+    @copydoc push_back(basic_json&&)
+    */
+    reference operator+=(const basic_json &val)
+    {
+        push_back(val);
+        return *this;
+    }
 
-  - 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
+    /*!
+    @brief add an object to an object
 
-  @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;
+    Inserts the given element @a val to the JSON object. If the function is
+    called on a JSON null value, an empty object is created before inserting
+    @a val.
 
-    switch (m_type)
-    {
-      case value_t::object:
-      {
-        if (m_value.object->empty())
-        {
-          o << "{}";
-          return;
-        }
+    @param[in] val the value to add to the JSON object
 
-        o << "{";
+    @throw std::domain_error when called on a type other than JSON object or
+    null; example: `"cannot use push_back() with number"`
 
-        // increase indentation
-        if (pretty_print)
-        {
-          new_indent += indent_step;
-          o << "\n";
-        }
+    @complexity Logarithmic in the size of the container, O(log(`size()`)).
 
-        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);
-        }
+    @liveexample{The example shows how `push_back()` and `+=` can be used to
+    add elements to a JSON object. Note how the `null` value was silently
+    converted to a JSON object.,push_back__object_t__value}
 
-        // decrease indentation
-        if (pretty_print)
+    @since version 1.0.0
+    */
+    void push_back(const typename object_t::value_type &val)
+    {
+        // push_back only works for null objects or objects
+        if (not(is_null() or is_object()))
         {
-          new_indent -= indent_step;
-          o << "\n";
+            JSON_THROW(std::domain_error("cannot use push_back() with " +
+                                         type_name()));
         }
 
-        o << string_t(new_indent, ' ') + "}";
-        return;
-      }
-
-      case value_t::array:
-      {
-        if (m_value.array->empty())
+        // transform null object into an object
+        if (is_null())
         {
-          o << "[]";
-          return;
+            m_type = value_t::object;
+            m_value = value_t::object;
+            assert_invariant();
         }
 
-        o << "[";
+        // add element to array
+        m_value.object->insert(val);
+    }
 
-        // increase indentation
-        if (pretty_print)
-        {
-          new_indent += indent_step;
-          o << "\n";
-        }
+    /*!
+    @brief add an object to an object
+    @copydoc push_back(const typename object_t::value_type&)
+    */
+    reference operator+=(const typename object_t::value_type &val)
+    {
+        push_back(val);
+        return *this;
+    }
 
-        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);
-        }
+    /*!
+    @brief add an object to an object
 
-        // decrease indentation
-        if (pretty_print)
-        {
-          new_indent -= indent_step;
-          o << "\n";
-        }
+    This function allows to use `push_back` with an initializer list. In case
 
-        o << string_t(new_indent, ' ') << "]";
-        return;
-      }
+    1. the current value is an object,
+    2. the initializer list @a init contains only two elements, and
+    3. the first element of @a init is a string,
 
-      case value_t::string:
-      {
-        o << string_t("\"") << escape_string(*m_value.string) << "\"";
-        return;
-      }
+    @a init is converted into an object element and added using
+    @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&&).
 
-      case value_t::boolean:
-      {
-        o << (m_value.boolean ? "true" : "false");
-        return;
-      }
+    @param init  an initializer list
 
-      case value_t::number_integer:
-      {
-        o << m_value.number_integer;
-        return;
-      }
+    @complexity Linear in the size of the initializer list @a init.
 
-      case value_t::number_unsigned:
-      {
-        o << m_value.number_unsigned;
-        return;
-      }
+    @note This function is required to resolve an ambiguous overload error,
+          because pairs like `{"key", "value"}` can be both interpreted as
+          `object_t::value_type` or `std::initializer_list<basic_json>`, see
+          https://github.com/nlohmann/json/issues/235 for more information.
 
-      case value_t::number_float:
-      {
-        if (m_value.number_float == 0)
+    @liveexample{The example shows how initializer lists are treated as
+    objects when possible.,push_back__initializer_list}
+    */
+    void push_back(std::initializer_list<basic_json> init)
+    {
+        if (is_object() and init.size() == 2 and init.begin()->is_string())
         {
-          // special case for zero to get "0.0"/"-0.0"
-          o << (std::signbit(m_value.number_float) ? "-0.0" : "0.0");
+            const string_t key = *init.begin();
+            push_back(typename object_t::value_type(key, *(init.begin() + 1)));
         }
         else
         {
-          o << m_value.number_float;
+            push_back(basic_json(init));
         }
-        return;
-      }
-
-      case value_t::discarded:
-      {
-        o << "<discarded>";
-        return;
-      }
-
-      case value_t::null:
-      {
-        o << "null";
-        return;
-      }
     }
-  }
 
-private:
-  //////////////////////
-  // member variables //
-  //////////////////////
+    /*!
+    @brief add an object to an object
+    @copydoc push_back(std::initializer_list<basic_json>)
+    */
+    reference operator+=(std::initializer_list<basic_json> init)
+    {
+        push_back(init);
+        return *this;
+    }
 
-  /// the type of the current element
-  value_t m_type = value_t::null;
+    /*!
+    @brief add an object to an array
 
-  /// the value of the current element
-  json_value m_value = {};
+    Creates a JSON value from the passed parameters @a args to the end of the
+    JSON value. If the function is called on a JSON null value, an empty array
+    is created before appending the value created from @a args.
 
-private:
-  ///////////////
-  // iterators //
-  ///////////////
-
-  /*!
-  @brief an iterator for primitive JSON types
-
-  This class models an iterator for primitive JSON types (boolean, number,
-  string). It's only purpose is to allow the iterator/const_iterator classes
-  to "iterate" over primitive values. Internally, the iterator is modeled by
-  a `difference_type` variable. Value begin_value (`0`) models the begin,
-  end_value (`1`) models past the end.
-  */
-  class primitive_iterator_t
-  {
-  public:
-    /// set iterator to a defined beginning
-    void set_begin() noexcept { m_it = begin_value; }
-
-    /// set iterator to a defined past the end
-    void set_end() noexcept { m_it = end_value; }
-
-    /// return whether the iterator can be dereferenced
-    constexpr bool is_begin() const noexcept { return (m_it == begin_value); }
-
-    /// 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; }
-
-    /// return value to compare
-    constexpr operator difference_type() const noexcept { return m_it; }
-
-  private:
-    static constexpr difference_type begin_value = 0;
-    static constexpr difference_type end_value = begin_value + 1;
-
-    /// iterator as signed integer type
-    difference_type m_it = std::numeric_limits<std::ptrdiff_t>::denorm_min();
-  };
-
-  /*!
-  @brief an iterator value
-
-  @note This structure could easily be a union, but MSVC currently does not
-  allow unions members with complex constructors, see
-  https://github.com/nlohmann/json/pull/105.
-  */
-  struct internal_iterator
-  {
-    /// iterator for JSON objects
-    typename object_t::iterator object_iterator;
-    /// iterator for JSON arrays
-    typename array_t::iterator array_iterator;
-    /// generic iterator for all other types
-    primitive_iterator_t primitive_iterator;
-
-    /// create an uninitialized internal_iterator
-    internal_iterator() noexcept : object_iterator(),
-                                   array_iterator(),
-                                   primitive_iterator()
-    {
-    }
-  };
-
-  /// proxy class for the iterator_wrapper functions
-  template <typename IteratorType> class iteration_proxy
-  {
-  private:
-    /// helper class for iteration
-    class iteration_proxy_internal
-    {
-    private:
-      /// the iterator
-      IteratorType anchor;
-      /// an index for arrays (used to create key names)
-      size_t array_index = 0;
+    @param[in] args arguments to forward to a constructor of @ref basic_json
+    @tparam Args compatible types to create a @ref basic_json object
 
-    public:
-      explicit iteration_proxy_internal(IteratorType it) noexcept : anchor(it)
-      {
-      }
+    @throw std::domain_error when called on a type other than JSON array or
+    null; example: `"cannot use emplace_back() with number"`
 
-      /// dereference operator (needed for range-based for)
-      iteration_proxy_internal &operator*() { return *this; }
+    @complexity Amortized constant.
 
-      /// increment operator (needed for range-based for)
-      iteration_proxy_internal &operator++()
-      {
-        ++anchor;
-        ++array_index;
+    @liveexample{The example shows how `push_back()` can be used to add
+    elements to a JSON array. Note how the `null` value was silently converted
+    to a JSON array.,emplace_back}
 
-        return *this;
-      }
-
-      /// inequality operator (needed for range-based for)
-      bool operator!=(const iteration_proxy_internal &o) const
-      {
-        return anchor != o.anchor;
-      }
-
-      /// return key of the iterator
-      typename basic_json::string_t key() const
-      {
-        assert(anchor.m_object != nullptr);
-
-        switch (anchor.m_object->type())
-        {
-          // use integer array index as key
-          case value_t::array:
-          {
-            return std::to_string(array_index);
-          }
-
-          // use key from the object
-          case value_t::object:
-          {
-            return anchor.key();
-          }
-
-          // use an empty key for all primitive types
-          default:
-          {
-            return "";
-          }
-        }
-      }
-
-      /// return value of the iterator
-      typename IteratorType::reference value() const { return anchor.value(); }
-    };
+    @since version 2.0.8
+    */
+    template <class... Args>
+    void emplace_back(Args &&... args)
+    {
+        // 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()));
+        }
 
-    /// the container to iterate
-    typename IteratorType::reference container;
+        // transform null object into an array
+        if (is_null())
+        {
+            m_type = value_t::array;
+            m_value = value_t::array;
+            assert_invariant();
+        }
 
-  public:
-    /// construct iteration proxy from a container
-    explicit iteration_proxy(typename IteratorType::reference cont)
-        : container(cont)
-    {
+        // add element to array (perfect forwarding)
+        m_value.array->emplace_back(std::forward<Args>(args)...);
     }
 
-    /// return iterator begin (needed for range-based for)
-    iteration_proxy_internal begin() noexcept
-    {
-      return iteration_proxy_internal(container.begin());
-    }
+    /*!
+    @brief add an object to an object if key does not exist
 
-    /// return iterator end (needed for range-based for)
-    iteration_proxy_internal end() noexcept
-    {
-      return iteration_proxy_internal(container.end());
-    }
-  };
+    Inserts a new element into a JSON object constructed in-place with the
+    given @a args if there is no element with the key in the container. If the
+    function is called on a JSON null value, an empty object is created before
+    appending the value created from @a args.
 
-public:
-  /*!
-  @brief a template for a random access iterator for the @ref basic_json class
-
-  This class implements a both iterators (iterator and const_iterator) for the
-  @ref basic_json class.
-
-  @note An iterator is called *initialized* when a pointer to a JSON value
-        has been set (e.g., by a constructor or a copy assignment). If the
-        iterator is default-constructed, it is *uninitialized* and most
-        methods are undefined. **The library uses assertions to detect calls
-        on uninitialized iterators.**
-
-  @requirement The class satisfies the following concept requirements:
-  -
-  [RandomAccessIterator](http://en.cppreference.com/w/cpp/concept/RandomAccessIterator):
-    The iterator that can be moved to point (forward and backward) to any
-    element in constant time.
-
-  @since version 1.0.0, simplified in version 2.0.9
-  */
-  template <typename U>
-  class iter_impl : public std::iterator<std::random_access_iterator_tag, U>
-  {
-    /// allow basic_json to access private members
-    friend class basic_json;
-
-    // make sure U is basic_json or const basic_json
-    static_assert(std::is_same<U, basic_json>::value or
-                      std::is_same<U, const basic_json>::value,
-                  "iter_impl only accepts (const) basic_json");
-
-  public:
-    /// the type of the values when the iterator is dereferenced
-    using value_type = typename basic_json::value_type;
-    /// a type to represent differences between iterators
-    using difference_type = typename basic_json::difference_type;
-    /// defines a pointer to the type iterated over (value_type)
-    using pointer =
-        typename std::conditional<std::is_const<U>::value,
-                                  typename basic_json::const_pointer,
-                                  typename basic_json::pointer>::type;
-    /// defines a reference to the type iterated over (value_type)
-    using reference =
-        typename std::conditional<std::is_const<U>::value,
-                                  typename basic_json::const_reference,
-                                  typename basic_json::reference>::type;
-    /// the category of the iterator
-    using iterator_category = std::bidirectional_iterator_tag;
+    @param[in] args arguments to forward to a constructor of @ref basic_json
+    @tparam Args compatible types to create a @ref basic_json object
 
-    /// default constructor
-    iter_impl() = default;
+    @return a pair consisting of an iterator to the inserted element, or the
+            already-existing element if no insertion happened, and a bool
+            denoting whether the insertion took place.
 
-    /*!
-    @brief constructor for a given JSON instance
-    @param[in] object  pointer to a JSON object for this iterator
-    @pre object != nullptr
-    @post The iterator is initialized; i.e. `m_object != nullptr`.
+    @throw std::domain_error 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()`)).
+
+    @liveexample{The example shows how `emplace()` can be used to add elements
+    to a JSON object. Note how the `null` value was silently converted to a
+    JSON object. Further note how no value is added if there was already one
+    value stored with the same key.,emplace}
+
+    @since version 2.0.8
     */
-    explicit iter_impl(pointer object) noexcept : m_object(object)
+    template <class... Args>
+    std::pair<iterator, bool> emplace(Args &&... args)
     {
-      assert(m_object != nullptr);
-
-      switch (m_object->m_type)
-      {
-        case basic_json::value_t::object:
+        // emplace only works for null objects or arrays
+        if (not(is_null() or is_object()))
         {
-          m_it.object_iterator = typename object_t::iterator();
-          break;
+            JSON_THROW(
+                std::domain_error("cannot use emplace() with " + type_name()));
         }
 
-        case basic_json::value_t::array:
+        // transform null object into an object
+        if (is_null())
         {
-          m_it.array_iterator = typename array_t::iterator();
-          break;
+            m_type = value_t::object;
+            m_value = value_t::object;
+            assert_invariant();
         }
 
-        default:
-        {
-          m_it.primitive_iterator = primitive_iterator_t();
-          break;
-        }
-      }
+        // add element to array (perfect forwarding)
+        auto res = m_value.object->emplace(std::forward<Args>(args)...);
+        // create result iterator and set iterator to the result of emplace
+        auto it = begin();
+        it.m_it.object_iterator = res.first;
+
+        // return pair of iterator and boolean
+        return {it, res.second};
     }
 
-    /*
-    Use operator `const_iterator` instead of `const_iterator(const iterator&
-    other) noexcept` to avoid two class definitions for @ref iterator and
-    @ref const_iterator.
+    /*!
+    @brief inserts element
 
-    This function is only called if this class is an @ref iterator. If this
-    class is a @ref const_iterator this function is not called.
-    */
-    operator const_iterator() const
-    {
-      const_iterator ret;
+    Inserts element @a val before iterator @a pos.
 
-      if (m_object)
-      {
-        ret.m_object = m_object;
-        ret.m_it = m_it;
-      }
+    @param[in] pos iterator before which the content will be inserted; may be
+    the end() iterator
+    @param[in] val element to insert
+    @return iterator pointing to the inserted @a val.
 
-      return ret;
-    }
+    @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"`
 
-    /*!
-    @brief copy constructor
-    @param[in] other  iterator to copy from
-    @note It is not checked whether @a other is initialized.
+    @complexity Constant plus linear in the distance between pos and end of
+    the container.
+
+    @liveexample{The example shows how `insert()` is used.,insert}
+
+    @since version 1.0.0
     */
-    iter_impl(const iter_impl &other) noexcept : m_object(other.m_object),
-                                                 m_it(other.m_it)
+    iterator insert(const_iterator pos, const basic_json &val)
     {
+        // insert only works for arrays
+        if (is_array())
+        {
+            // 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"));
+            }
+
+            // insert to array and return iterator
+            iterator result(this);
+            result.m_it.array_iterator =
+                m_value.array->insert(pos.m_it.array_iterator, val);
+            return result;
+        }
+
+        JSON_THROW(
+            std::domain_error("cannot use insert() with " + type_name()));
     }
 
     /*!
-    @brief copy assignment
-    @param[in,out] other  iterator to copy from
-    @note It is not checked whether @a other is initialized.
+    @brief inserts element
+    @copydoc insert(const_iterator, const basic_json&)
     */
-    iter_impl &operator=(iter_impl other) noexcept(
-        std::is_nothrow_move_constructible<pointer>::value
-            and std::is_nothrow_move_assignable<pointer>::value and
-                std::is_nothrow_move_constructible<internal_iterator>::value and
-                    std::is_nothrow_move_assignable<internal_iterator>::value)
+    iterator insert(const_iterator pos, basic_json &&val)
     {
-      std::swap(m_object, other.m_object);
-      std::swap(m_it, other.m_it);
-      return *this;
+        return insert(pos, val);
     }
 
-  private:
     /*!
-    @brief set the iterator to the first value
-    @pre The iterator is initialized; i.e. `m_object != nullptr`.
-    */
-    void set_begin() noexcept
-    {
-      assert(m_object != nullptr);
+    @brief inserts elements
 
-      switch (m_object->m_type)
-      {
-        case basic_json::value_t::object:
-        {
-          m_it.object_iterator = m_object->m_value.object->begin();
-          break;
-        }
+    Inserts @a cnt copies of @a val before iterator @a pos.
 
-        case basic_json::value_t::array:
-        {
-          m_it.array_iterator = m_object->m_value.array->begin();
-          break;
-        }
+    @param[in] pos iterator before which the content will be inserted; may be
+    the end() iterator
+    @param[in] cnt number of copies of @a val to insert
+    @param[in] val element to insert
+    @return iterator pointing to the first element inserted, or @a pos if
+    `cnt==0`
 
-        case basic_json::value_t::null:
-        {
-          // set to end so begin()==end() is true: null is empty
-          m_it.primitive_iterator.set_end();
-          break;
-        }
+    @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"`
 
-        default:
-        {
-          m_it.primitive_iterator.set_begin();
-          break;
-        }
-      }
-    }
+    @complexity Linear in @a cnt plus linear in the distance between @a pos
+    and end of the container.
 
-    /*!
-    @brief set the iterator past the last value
-    @pre The iterator is initialized; i.e. `m_object != nullptr`.
+    @liveexample{The example shows how `insert()` is used.,insert__count}
+
+    @since version 1.0.0
     */
-    void set_end() noexcept
+    iterator insert(const_iterator pos, size_type cnt, const basic_json &val)
     {
-      assert(m_object != nullptr);
-
-      switch (m_object->m_type)
-      {
-        case basic_json::value_t::object:
+        // insert only works for arrays
+        if (is_array())
         {
-          m_it.object_iterator = m_object->m_value.object->end();
-          break;
-        }
+            // 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"));
+            }
 
-        case basic_json::value_t::array:
-        {
-          m_it.array_iterator = m_object->m_value.array->end();
-          break;
+            // insert to array and return iterator
+            iterator result(this);
+            result.m_it.array_iterator =
+                m_value.array->insert(pos.m_it.array_iterator, cnt, val);
+            return result;
         }
 
-        default:
-        {
-          m_it.primitive_iterator.set_end();
-          break;
-        }
-      }
+        JSON_THROW(
+            std::domain_error("cannot use insert() with " + type_name()));
     }
 
-  public:
     /*!
-    @brief return a reference to the value pointed to by the iterator
-    @pre The iterator is initialized; i.e. `m_object != nullptr`.
+    @brief inserts elements
+
+    Inserts elements from range `[first, last)` before iterator @a pos.
+
+    @param[in] pos iterator before which the content will be inserted; may be
+    the end() iterator
+    @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
+    container for which insert is called; example: `"passed iterators may not
+    belong to container"`
+
+    @return iterator pointing to the first element inserted, or @a pos if
+    `first==last`
+
+    @complexity Linear in `std::distance(first, last)` plus linear in the
+    distance between @a pos and end of the container.
+
+    @liveexample{The example shows how `insert()` is used.,insert__range}
+
+    @since version 1.0.0
     */
-    reference operator*() const
+    iterator insert(const_iterator pos, const_iterator first,
+                    const_iterator last)
     {
-      assert(m_object != nullptr);
-
-      switch (m_object->m_type)
-      {
-        case basic_json::value_t::object:
+        // insert only works for arrays
+        if (not is_array())
         {
-          assert(m_it.object_iterator != m_object->m_value.object->end());
-          return m_it.object_iterator->second;
+            JSON_THROW(
+                std::domain_error("cannot use insert() with " + type_name()));
         }
 
-        case basic_json::value_t::array:
+        // check if iterator pos fits to this JSON value
+        if (pos.m_object != this)
         {
-          assert(m_it.array_iterator != m_object->m_value.array->end());
-          return *m_it.array_iterator;
+            JSON_THROW(
+                std::domain_error("iterator does not fit current value"));
         }
 
-        case basic_json::value_t::null:
+        // check if range iterators belong to the same JSON object
+        if (first.m_object != last.m_object)
         {
-          JSON_THROW(std::out_of_range("cannot get value"));
+            JSON_THROW(std::domain_error("iterators do not fit"));
         }
 
-        default:
+        if (first.m_object == this or last.m_object == this)
         {
-          if (m_it.primitive_iterator.is_begin())
-          {
-            return *m_object;
-          }
-
-          JSON_THROW(std::out_of_range("cannot get value"));
+            JSON_THROW(std::domain_error(
+                "passed iterators may not belong to container"));
         }
-      }
+
+        // insert to array and return iterator
+        iterator result(this);
+        result.m_it.array_iterator = m_value.array->insert(
+            pos.m_it.array_iterator, first.m_it.array_iterator,
+            last.m_it.array_iterator);
+        return result;
     }
 
     /*!
-    @brief dereference the iterator
-    @pre The iterator is initialized; i.e. `m_object != nullptr`.
-    */
-    pointer operator->() const
-    {
-      assert(m_object != nullptr);
+    @brief inserts elements
 
-      switch (m_object->m_type)
-      {
-        case basic_json::value_t::object:
-        {
-          assert(m_it.object_iterator != m_object->m_value.object->end());
-          return &(m_it.object_iterator->second);
-        }
+    Inserts elements from initializer list @a ilist before iterator @a pos.
 
-        case basic_json::value_t::array:
-        {
-          assert(m_it.array_iterator != m_object->m_value.array->end());
-          return &*m_it.array_iterator;
-        }
+    @param[in] pos iterator before which the content will be inserted; may be
+    the end() iterator
+    @param[in] ilist initializer list to insert the values from
 
-        default:
-        {
-          if (m_it.primitive_iterator.is_begin())
-          {
-            return m_object;
-          }
+    @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"`
 
-          JSON_THROW(std::out_of_range("cannot get value"));
-        }
-      }
-    }
+    @return iterator pointing to the first element inserted, or @a pos if
+    `ilist` is empty
 
-    /*!
-    @brief post-increment (it++)
-    @pre The iterator is initialized; i.e. `m_object != nullptr`.
-    */
-    iter_impl operator++(int)
-    {
-      auto result = *this;
-      ++(*this);
-      return result;
-    }
+    @complexity Linear in `ilist.size()` plus linear in the distance between
+    @a pos and end of the container.
 
-    /*!
-    @brief pre-increment (++it)
-    @pre The iterator is initialized; i.e. `m_object != nullptr`.
+    @liveexample{The example shows how `insert()` is used.,insert__ilist}
+
+    @since version 1.0.0
     */
-    iter_impl &operator++()
+    iterator insert(const_iterator pos, std::initializer_list<basic_json> ilist)
     {
-      assert(m_object != nullptr);
-
-      switch (m_object->m_type)
-      {
-        case basic_json::value_t::object:
+        // insert only works for arrays
+        if (not is_array())
         {
-          std::advance(m_it.object_iterator, 1);
-          break;
+            JSON_THROW(
+                std::domain_error("cannot use insert() with " + type_name()));
         }
 
-        case basic_json::value_t::array:
-        {
-          std::advance(m_it.array_iterator, 1);
-          break;
-        }
-
-        default:
+        // check if iterator pos fits to this JSON value
+        if (pos.m_object != this)
         {
-          ++m_it.primitive_iterator;
-          break;
+            JSON_THROW(
+                std::domain_error("iterator does not fit current value"));
         }
-      }
 
-      return *this;
+        // insert to array and return iterator
+        iterator result(this);
+        result.m_it.array_iterator =
+            m_value.array->insert(pos.m_it.array_iterator, ilist);
+        return result;
     }
 
     /*!
-    @brief post-decrement (it--)
-    @pre The iterator is initialized; i.e. `m_object != nullptr`.
-    */
-    iter_impl operator--(int)
-    {
-      auto result = *this;
-      --(*this);
-      return result;
-    }
+    @brief exchanges the values
 
-    /*!
-    @brief pre-decrement (--it)
-    @pre The iterator is initialized; i.e. `m_object != nullptr`.
-    */
-    iter_impl &operator--()
-    {
-      assert(m_object != nullptr);
+    Exchanges the contents of the JSON value with those of @a other. Does not
+    invoke any move, copy, or swap operations on individual elements. All
+    iterators and references remain valid. The past-the-end iterator is
+    invalidated.
 
-      switch (m_object->m_type)
-      {
-        case basic_json::value_t::object:
-        {
-          std::advance(m_it.object_iterator, -1);
-          break;
-        }
+    @param[in,out] other JSON value to exchange the contents with
 
-        case basic_json::value_t::array:
-        {
-          std::advance(m_it.array_iterator, -1);
-          break;
-        }
+    @complexity Constant.
 
-        default:
-        {
-          --m_it.primitive_iterator;
-          break;
-        }
-      }
+    @liveexample{The example below shows how JSON values can be swapped with
+    `swap()`.,swap__reference}
 
-      return *this;
+    @since version 1.0.0
+    */
+    void swap(reference other) noexcept(
+        std::is_nothrow_move_constructible<value_t>::value
+            and std::is_nothrow_move_assignable<value_t>::value
+                and std::is_nothrow_move_constructible<json_value>::value
+                    and std::is_nothrow_move_assignable<json_value>::value)
+    {
+        std::swap(m_type, other.m_type);
+        std::swap(m_value, other.m_value);
+        assert_invariant();
     }
 
     /*!
-    @brief  comparison: equal
-    @pre The iterator is initialized; i.e. `m_object != nullptr`.
-    */
-    bool operator==(const iter_impl &other) const
-    {
-      // 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"));
-      }
+    @brief exchanges the values
 
-      assert(m_object != nullptr);
+    Exchanges the contents of a JSON array with those of @a other. Does not
+    invoke any move, copy, or swap operations on individual elements. All
+    iterators and references remain valid. The past-the-end iterator is
+    invalidated.
 
-      switch (m_object->m_type)
-      {
-        case basic_json::value_t::object:
-        {
-          return (m_it.object_iterator == other.m_it.object_iterator);
-        }
+    @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"`
+
+    @complexity Constant.
+
+    @liveexample{The example below shows how arrays can be swapped with
+    `swap()`.,swap__array_t}
 
-        case basic_json::value_t::array:
+    @since version 1.0.0
+    */
+    void swap(array_t &other)
+    {
+        // swap only works for arrays
+        if (is_array())
         {
-          return (m_it.array_iterator == other.m_it.array_iterator);
+            std::swap(*(m_value.array), other);
         }
-
-        default:
+        else
         {
-          return (m_it.primitive_iterator == other.m_it.primitive_iterator);
+            JSON_THROW(
+                std::domain_error("cannot use swap() with " + type_name()));
         }
-      }
     }
 
     /*!
-    @brief  comparison: not equal
-    @pre The iterator is initialized; i.e. `m_object != nullptr`.
-    */
-    bool operator!=(const iter_impl &other) const
-    {
-      return not operator==(other);
-    }
+    @brief exchanges the values
 
-    /*!
-    @brief  comparison: smaller
-    @pre The iterator is initialized; i.e. `m_object != nullptr`.
-    */
-    bool operator<(const iter_impl &other) const
-    {
-      // 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"));
-      }
+    Exchanges the contents of a JSON object with those of @a other. Does not
+    invoke any move, copy, or swap operations on individual elements. All
+    iterators and references remain valid. The past-the-end iterator is
+    invalidated.
 
-      assert(m_object != nullptr);
+    @param[in,out] other object to exchange the contents with
 
-      switch (m_object->m_type)
-      {
-        case basic_json::value_t::object:
-        {
-          JSON_THROW(
-              std::domain_error("cannot compare order of object iterators"));
-        }
+    @throw std::domain_error when JSON value is not an object; example:
+    `"cannot use swap() with string"`
+
+    @complexity Constant.
+
+    @liveexample{The example below shows how objects can be swapped with
+    `swap()`.,swap__object_t}
 
-        case basic_json::value_t::array:
+    @since version 1.0.0
+    */
+    void swap(object_t &other)
+    {
+        // swap only works for objects
+        if (is_object())
         {
-          return (m_it.array_iterator < other.m_it.array_iterator);
+            std::swap(*(m_value.object), other);
         }
-
-        default:
+        else
         {
-          return (m_it.primitive_iterator < other.m_it.primitive_iterator);
+            JSON_THROW(
+                std::domain_error("cannot use swap() with " + type_name()));
         }
-      }
     }
 
     /*!
-    @brief  comparison: less than or equal
-    @pre The iterator is initialized; i.e. `m_object != nullptr`.
-    */
-    bool operator<=(const iter_impl &other) const
-    {
-      return not other.operator<(*this);
-    }
+    @brief exchanges the values
 
-    /*!
-    @brief  comparison: greater than
-    @pre The iterator is initialized; i.e. `m_object != nullptr`.
-    */
-    bool operator>(const iter_impl &other) const
-    {
-      return not operator<=(other);
-    }
+    Exchanges the contents of a JSON string with those of @a other. Does not
+    invoke any move, copy, or swap operations on individual elements. All
+    iterators and references remain valid. The past-the-end iterator is
+    invalidated.
 
-    /*!
-    @brief  comparison: greater than or equal
-    @pre The iterator is initialized; i.e. `m_object != nullptr`.
-    */
-    bool operator>=(const iter_impl &other) const
-    {
-      return not operator<(other);
-    }
+    @param[in,out] other string to exchange the contents with
 
-    /*!
-    @brief  add to iterator
-    @pre The iterator is initialized; i.e. `m_object != nullptr`.
+    @throw std::domain_error when JSON value is not a string; example: `"cannot
+    use swap() with boolean"`
+
+    @complexity Constant.
+
+    @liveexample{The example below shows how strings can be swapped with
+    `swap()`.,swap__string_t}
+
+    @since version 1.0.0
     */
-    iter_impl &operator+=(difference_type i)
+    void swap(string_t &other)
     {
-      assert(m_object != nullptr);
-
-      switch (m_object->m_type)
-      {
-        case basic_json::value_t::object:
+        // swap only works for strings
+        if (is_string())
         {
-          JSON_THROW(
-              std::domain_error("cannot use offsets with object iterators"));
+            std::swap(*(m_value.string), other);
         }
-
-        case basic_json::value_t::array:
+        else
         {
-          std::advance(m_it.array_iterator, i);
-          break;
+            JSON_THROW(
+                std::domain_error("cannot use swap() with " + type_name()));
         }
+    }
 
-        default:
-        {
-          m_it.primitive_iterator += i;
-          break;
-        }
-      }
+    /// @}
 
-      return *this;
-    }
+    //////////////////////////////////////////
+    // lexicographical comparison operators //
+    //////////////////////////////////////////
 
-    /*!
-    @brief  subtract from iterator
-    @pre The iterator is initialized; i.e. `m_object != nullptr`.
-    */
-    iter_impl &operator-=(difference_type i) { return operator+=(-i); }
+    /// @name lexicographical comparison operators
+    /// @{
 
+private:
     /*!
-    @brief  add to iterator
-    @pre The iterator is initialized; i.e. `m_object != nullptr`.
-    */
-    iter_impl operator+(difference_type i)
-    {
-      auto result = *this;
-      result += i;
-      return result;
-    }
+    @brief comparison operator for JSON types
 
-    /*!
-    @brief  subtract from iterator
-    @pre The iterator is initialized; i.e. `m_object != nullptr`.
-    */
-    iter_impl operator-(difference_type i)
-    {
-      auto result = *this;
-      result -= i;
-      return result;
-    }
+    Returns an ordering that is similar to Python:
+    - order: null < boolean < number < object < array < string
+    - furthermore, each type is not smaller than itself
 
-    /*!
-    @brief  return difference
-    @pre The iterator is initialized; i.e. `m_object != nullptr`.
+    @since version 1.0.0
     */
-    difference_type operator-(const iter_impl &other) const
+    friend bool operator<(const value_t lhs, const value_t rhs) noexcept
     {
-      assert(m_object != nullptr);
+        static constexpr std::array<uint8_t, 8> order = {{
+            0, // null
+            3, // object
+            4, // array
+            5, // string
+            1, // boolean
+            2, // integer
+            2, // unsigned
+            2, // float
+        }};
 
-      switch (m_object->m_type)
-      {
-        case basic_json::value_t::object:
+        // discarded values are not comparable
+        if (lhs == value_t::discarded or rhs == value_t::discarded)
         {
-          JSON_THROW(
-              std::domain_error("cannot use offsets with object iterators"));
+            return false;
         }
 
-        case basic_json::value_t::array:
-        {
-          return m_it.array_iterator - other.m_it.array_iterator;
-        }
-
-        default:
-        {
-          return m_it.primitive_iterator - other.m_it.primitive_iterator;
-        }
-      }
-    }
+        return order[static_cast<std::size_t>(lhs)] <
+               order[static_cast<std::size_t>(rhs)];
+    }
 
+public:
     /*!
-    @brief  access to successor
-    @pre The iterator is initialized; i.e. `m_object != nullptr`.
+    @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.
+    - 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.
+
+    @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
     */
-    reference operator[](difference_type n) const
+    friend bool operator==(const_reference lhs, const_reference rhs) noexcept
     {
-      assert(m_object != nullptr);
+        const auto lhs_type = lhs.type();
+        const auto rhs_type = rhs.type();
 
-      switch (m_object->m_type)
-      {
-        case basic_json::value_t::object:
+        if (lhs_type == rhs_type)
         {
-          JSON_THROW(
-              std::domain_error("cannot use operator[] for object iterators"));
+            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;
+            }
+            }
         }
-
-        case basic_json::value_t::array:
+        else if (lhs_type == value_t::number_integer and
+                 rhs_type == value_t::number_float)
         {
-          return *std::next(m_it.array_iterator, n);
+            return static_cast<number_float_t>(lhs.m_value.number_integer) ==
+                   rhs.m_value.number_float;
         }
-
-        case basic_json::value_t::null:
+        else if (lhs_type == value_t::number_float and
+                 rhs_type == value_t::number_integer)
         {
-          JSON_THROW(std::out_of_range("cannot get value"));
+            return lhs.m_value.number_float ==
+                   static_cast<number_float_t>(rhs.m_value.number_integer);
         }
-
-        default:
+        else if (lhs_type == value_t::number_unsigned and
+                 rhs_type == value_t::number_float)
         {
-          if (m_it.primitive_iterator == -n)
-          {
-            return *m_object;
-          }
-
-          JSON_THROW(std::out_of_range("cannot get value"));
+            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);
         }
-      }
-    }
-
-    /*!
-    @brief  return the key of an object iterator
-    @pre The iterator is initialized; i.e. `m_object != nullptr`.
-    */
-    typename object_t::key_type key() const
-    {
-      assert(m_object != nullptr);
-
-      if (m_object->is_object())
-      {
-        return m_it.object_iterator->first;
-      }
 
-      JSON_THROW(
-          std::domain_error("cannot use key() for non-object iterators"));
+        return false;
     }
 
     /*!
-    @brief  return the value of an iterator
-    @pre The iterator is initialized; i.e. `m_object != nullptr`.
-    */
-    reference value() const { return operator*(); }
+    @brief comparison: equal
 
-  private:
-    /// associated JSON instance
-    pointer m_object = nullptr;
-    /// the actual iterator of the associated instance
-    internal_iterator m_it = internal_iterator();
-  };
+    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 a template for a reverse iterator class
+    @param[in] v  JSON value to consider
+    @return whether @a v is null
 
-  @tparam Base the base iterator type to reverse. Valid types are @ref
-  iterator (to create @ref reverse_iterator) and @ref const_iterator (to
-  create @ref const_reverse_iterator).
-
-  @requirement The class satisfies the following concept requirements:
-  -
-  [RandomAccessIterator](http://en.cppreference.com/w/cpp/concept/RandomAccessIterator):
-    The iterator that can be moved to point (forward and backward) to any
-    element in constant time.
-  - [OutputIterator](http://en.cppreference.com/w/cpp/concept/OutputIterator):
-    It is possible to write to the pointed-to element (only if @a Base is
-    @ref iterator).
-
-  @since version 1.0.0
-  */
-  template <typename Base>
-  class json_reverse_iterator : public std::reverse_iterator<Base>
-  {
-  public:
-    /// shortcut to the reverse iterator adaptor
-    using base_iterator = std::reverse_iterator<Base>;
-    /// the reference type for the pointed-to element
-    using reference = typename Base::reference;
-
-    /// create reverse iterator from iterator
-    json_reverse_iterator(
-        const typename base_iterator::iterator_type &it) noexcept
-        : base_iterator(it)
-    {
-    }
+    @complexity Constant.
 
-    /// create reverse iterator from base class
-    json_reverse_iterator(const base_iterator &it) noexcept : base_iterator(it)
-    {
-    }
+    @liveexample{The example compares several JSON types to the null pointer.
+    ,operator__equal__nullptr_t}
 
-    /// post-increment (it++)
-    json_reverse_iterator operator++(int)
+    @since version 1.0.0
+    */
+    friend bool operator==(const_reference v, std::nullptr_t) noexcept
     {
-      return base_iterator::operator++(1);
+        return v.is_null();
     }
 
-    /// pre-increment (++it)
-    json_reverse_iterator &operator++()
+    /*!
+    @brief comparison: equal
+    @copydoc operator==(const_reference, std::nullptr_t)
+    */
+    friend bool operator==(std::nullptr_t, const_reference v) noexcept
     {
-      base_iterator::operator++();
-      return *this;
+        return v.is_null();
     }
 
-    /// post-decrement (it--)
-    json_reverse_iterator operator--(int)
-    {
-      return base_iterator::operator--(1);
-    }
+    /*!
+    @brief comparison: not equal
 
-    /// pre-decrement (--it)
-    json_reverse_iterator &operator--()
-    {
-      base_iterator::operator--();
-      return *this;
-    }
+    Compares two JSON values for inequality by calculating `not (lhs == rhs)`.
 
-    /// add to iterator
-    json_reverse_iterator &operator+=(difference_type i)
-    {
-      base_iterator::operator+=(i);
-      return *this;
-    }
+    @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
 
-    /// add to iterator
-    json_reverse_iterator operator+(difference_type i) const
-    {
-      auto result = *this;
-      result += i;
-      return result;
-    }
+    @complexity Linear.
 
-    /// subtract from iterator
-    json_reverse_iterator operator-(difference_type i) const
-    {
-      auto result = *this;
-      result -= i;
-      return result;
-    }
+    @liveexample{The example demonstrates comparing several JSON
+    types.,operator__notequal}
 
-    /// return difference
-    difference_type operator-(const json_reverse_iterator &other) const
+    @since version 1.0.0
+    */
+    friend bool operator!=(const_reference lhs, const_reference rhs) noexcept
     {
-      return this->base() - other.base();
+        return not(lhs == rhs);
     }
 
-    /// access to successor
-    reference operator[](difference_type n) const
-    {
-      return *(this->operator+(n));
-    }
+    /*!
+    @brief comparison: not equal
 
-    /// return the key of an object iterator
-    typename object_t::key_type key() const
-    {
-      auto it = --this->base();
-      return it.key();
-    }
+    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()`.
 
-    /// return the value of an iterator
-    reference value() const
-    {
-      auto it = --this->base();
-      return it.operator*();
-    }
-  };
+    @param[in] v  JSON value to consider
+    @return whether @a v is not null
 
-private:
-  //////////////////////
-  // lexer and parser //
-  //////////////////////
-
-  /*!
-  @brief lexical analysis
-
-  This class organizes the lexical analysis during JSON deserialization. The
-  core of it is a scanner generated by [re2c](http://re2c.org) that
-  processes a buffer and recognizes tokens according to RFC 7159.
-  */
-  class lexer
-  {
-  public:
-    /// 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 `}`
-      name_separator,  ///< the name separator `:`
-      value_separator, ///< the value separator `,`
-      parse_error,     ///< indicating a parse error
-      end_of_input     ///< indicating the end of the input buffer
-    };
+    @complexity Constant.
 
-    /// the char type to use in the lexer
-    using lexer_char_t = unsigned char;
+    @liveexample{The example compares several JSON types to the null pointer.
+    ,operator__notequal__nullptr_t}
 
-    /// a lexer from a buffer with given length
-    lexer(const lexer_char_t *buff, const size_t len) noexcept : m_content(buff)
+    @since version 1.0.0
+    */
+    friend bool operator!=(const_reference v, std::nullptr_t) noexcept
     {
-      assert(m_content != nullptr);
-      m_start = m_cursor = m_content;
-      m_limit = m_content + len;
+        return not v.is_null();
     }
 
-    /// a lexer from an input stream
-    explicit lexer(std::istream &s) : m_stream(&s), m_line_buffer()
+    /*!
+    @brief comparison: not equal
+    @copydoc operator!=(const_reference, std::nullptr_t)
+    */
+    friend bool operator!=(std::nullptr_t, const_reference v) noexcept
     {
-      // immediately abort if stream is erroneous
-      if (s.fail())
-      {
-        JSON_THROW(std::invalid_argument("stream error"));
-      }
-
-      // fill buffer
-      fill_line_buffer();
-
-      // skip UTF-8 byte-order mark
-      if (m_line_buffer.size() >= 3 and
-          m_line_buffer.substr(0, 3) == "\xEF\xBB\xBF")
-      {
-        m_line_buffer[0] = ' ';
-        m_line_buffer[1] = ' ';
-        m_line_buffer[2] = ' ';
-      }
+        return not v.is_null();
     }
 
-    // switch off unwanted functions (due to pointer members)
-    lexer() = delete;
-    lexer(const lexer &) = delete;
-    lexer operator=(const lexer &) = delete;
-
     /*!
-    @brief create a string from one or two Unicode code points
-
-    There are two cases: (1) @a codepoint1 is in the Basic Multilingual
-    Plane (U+0000 through U+FFFF) and @a codepoint2 is 0, or (2)
-    @a codepoint1 and @a codepoint2 are a UTF-16 surrogate pair to
-    represent a code point above U+FFFF.
+    @brief comparison: less than
 
-    @param[in] codepoint1  the code point (can be high surrogate)
-    @param[in] codepoint2  the code point (can be low surrogate or 0)
+    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).
 
-    @return string representation of the code point; the length of the
-    result string is between 1 and 4 characters.
+    @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
 
-    @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:
-    `""missing or wrong low surrogate""`
+    @complexity Linear.
 
-    @complexity Constant.
+    @liveexample{The example demonstrates comparing several JSON
+    types.,operator__less}
 
-    @see <http://en.wikipedia.org/wiki/UTF-8#Sample_code>
+    @since version 1.0.0
     */
-    static string_t to_unicode(const std::size_t codepoint1,
-                               const std::size_t codepoint2 = 0)
+    friend bool operator<(const_reference lhs, const_reference rhs) noexcept
     {
-      // calculate the code point from the given code points
-      std::size_t codepoint = codepoint1;
+        const auto lhs_type = lhs.type();
+        const auto rhs_type = rhs.type();
 
-      // check if codepoint1 is a high surrogate
-      if (codepoint1 >= 0xD800 and codepoint1 <= 0xDBFF)
-      {
-        // check if codepoint2 is a low surrogate
-        if (codepoint2 >= 0xDC00 and codepoint2 <= 0xDFFF)
-        {
-          codepoint =
-              // high surrogate occupies the most significant 22 bits
-              (codepoint1 << 10)
-              // low surrogate occupies the least significant 15 bits
-              + codepoint2
-              // there is still the 0xD800, 0xDC00 and 0x10000 noise
-              // in the result so we have to subtract with:
-              // (0xD800 << 10) + DC00 - 0x10000 = 0x35FDC00
-              - 0x35FDC00;
-        }
-        else
+        if (lhs_type == rhs_type)
         {
-          JSON_THROW(std::invalid_argument("missing or wrong low surrogate"));
-        }
-      }
-
-      string_t result;
-
-      if (codepoint < 0x80)
-      {
-        // 1-byte characters: 0xxxxxxx (ASCII)
-        result.append(1, static_cast<typename string_t::value_type>(codepoint));
-      }
-      else if (codepoint <= 0x7ff)
-      {
-        // 2-byte characters: 110xxxxx 10xxxxxx
-        result.append(1, static_cast<typename string_t::value_type>(
-                             0xC0 | ((codepoint >> 6) & 0x1F)));
-        result.append(1, static_cast<typename string_t::value_type>(
-                             0x80 | (codepoint & 0x3F)));
-      }
-      else if (codepoint <= 0xffff)
-      {
-        // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx
-        result.append(1, static_cast<typename string_t::value_type>(
-                             0xE0 | ((codepoint >> 12) & 0x0F)));
-        result.append(1, static_cast<typename string_t::value_type>(
-                             0x80 | ((codepoint >> 6) & 0x3F)));
-        result.append(1, static_cast<typename string_t::value_type>(
-                             0x80 | (codepoint & 0x3F)));
-      }
-      else if (codepoint <= 0x10ffff)
-      {
-        // 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
-        result.append(1, static_cast<typename string_t::value_type>(
-                             0xF0 | ((codepoint >> 18) & 0x07)));
-        result.append(1, static_cast<typename string_t::value_type>(
-                             0x80 | ((codepoint >> 12) & 0x3F)));
-        result.append(1, static_cast<typename string_t::value_type>(
-                             0x80 | ((codepoint >> 6) & 0x3F)));
-        result.append(1, static_cast<typename string_t::value_type>(
-                             0x80 | (codepoint & 0x3F)));
-      }
-      else
-      {
-        JSON_THROW(std::out_of_range("code points above 0x10FFFF are invalid"));
-      }
-
-      return result;
-    }
-
-    /// return name of values of type token_type (only used for errors)
-    static std::string token_type_name(const token_type t)
-    {
-      switch (t)
-      {
-        case token_type::uninitialized:
-          return "<uninitialized>";
-        case token_type::literal_true:
-          return "true literal";
-        case token_type::literal_false:
-          return "false literal";
-        case token_type::literal_null:
-          return "null literal";
-        case token_type::value_string:
-          return "string literal";
-        case token_type::value_number:
-          return "number literal";
-        case token_type::begin_array:
-          return "'['";
-        case token_type::begin_object:
-          return "'{'";
-        case token_type::end_array:
-          return "']'";
-        case token_type::end_object:
-          return "'}'";
-        case token_type::name_separator:
-          return "':'";
-        case token_type::value_separator:
-          return "','";
-        case token_type::parse_error:
-          return "<parse error>";
-        case token_type::end_of_input:
-          return "end of input";
-        default:
-        {
-          // catch non-enum values
-          return "unknown token"; // LCOV_EXCL_LINE
-        }
-      }
-    }
-
-    /*!
-    This function implements a scanner for JSON. It is specified using
-    regular expressions that try to follow RFC 7159 as close as possible.
-    These regular expressions are then translated into a minimized
-    deterministic finite automaton (DFA) by the tool
-    [re2c](http://re2c.org). As a result, the translated code for this
-    function consists of a large block of code with `goto` jumps.
-
-    @return the class of the next token read from the buffer
-
-    @complexity Linear in the length of the input.\n
-
-    Proposition: The loop below will always terminate for finite input.\n
-
-    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.
-    */
-    token_type scan()
-    {
-      while (true)
-      {
-        // pointer for backtracking information
-        m_marker = nullptr;
-
-        // remember the begin of the token
-        m_start = m_cursor;
-        assert(m_start != nullptr);
-
-        {
-          lexer_char_t yych;
-          unsigned int yyaccept = 0;
-          static const unsigned char yybm[] = {
-              0,   0,   0,   0,   0,   0,   0,   0,   0,   32,  32,  0,   0,
-              32,  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-              0,   0,   0,   0,   0,   0,   160, 128, 0,   128, 128, 128, 128,
-              128, 128, 128, 128, 128, 128, 128, 128, 128, 192, 192, 192, 192,
-              192, 192, 192, 192, 192, 192, 128, 128, 128, 128, 128, 128, 128,
-              128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
-              128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
-              128, 0,   128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
-              128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
-              128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 0,   0,
-              0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-              0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-              0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-              0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-              0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-              0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-              0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-              0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-              0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-              0,   0,   0,   0,   0,   0,   0,   0,   0,
-          };
-          if ((m_limit - m_cursor) < 5)
-          {
-            fill_line_buffer(5); // LCOV_EXCL_LINE
-          }
-          yych = *m_cursor;
-          if (yybm[0 + yych] & 32)
-          {
-            goto basic_json_parser_6;
-          }
-          if (yych <= '[')
-          {
-            if (yych <= '-')
-            {
-              if (yych <= '"')
-              {
-                if (yych <= 0x00)
-                {
-                  goto basic_json_parser_2;
-                }
-                if (yych <= '!')
-                {
-                  goto basic_json_parser_4;
-                }
-                goto basic_json_parser_9;
-              }
-              else
-              {
-                if (yych <= '+')
-                {
-                  goto basic_json_parser_4;
-                }
-                if (yych <= ',')
-                {
-                  goto basic_json_parser_10;
-                }
-                goto basic_json_parser_12;
-              }
-            }
-            else
-            {
-              if (yych <= '9')
-              {
-                if (yych <= '/')
-                {
-                  goto basic_json_parser_4;
-                }
-                if (yych <= '0')
-                {
-                  goto basic_json_parser_13;
-                }
-                goto basic_json_parser_15;
-              }
-              else
-              {
-                if (yych <= ':')
-                {
-                  goto basic_json_parser_17;
-                }
-                if (yych <= 'Z')
-                {
-                  goto basic_json_parser_4;
-                }
-                goto basic_json_parser_19;
-              }
-            }
-          }
-          else
-          {
-            if (yych <= 'n')
-            {
-              if (yych <= 'e')
-              {
-                if (yych == ']')
-                {
-                  goto basic_json_parser_21;
-                }
-                goto basic_json_parser_4;
-              }
-              else
-              {
-                if (yych <= 'f')
-                {
-                  goto basic_json_parser_23;
-                }
-                if (yych <= 'm')
-                {
-                  goto basic_json_parser_4;
-                }
-                goto basic_json_parser_24;
-              }
-            }
-            else
-            {
-              if (yych <= 'z')
-              {
-                if (yych == 't')
-                {
-                  goto basic_json_parser_25;
-                }
-                goto basic_json_parser_4;
-              }
-              else
-              {
-                if (yych <= '{')
-                {
-                  goto basic_json_parser_26;
-                }
-                if (yych == '}')
-                {
-                  goto basic_json_parser_28;
-                }
-                goto basic_json_parser_4;
-              }
-            }
-          }
-        basic_json_parser_2:
-          ++m_cursor;
-          {
-            last_token_type = token_type::end_of_input;
-            break;
-          }
-        basic_json_parser_4:
-          ++m_cursor;
-        basic_json_parser_5:
-        {
-          last_token_type = token_type::parse_error;
-          break;
-        }
-        basic_json_parser_6:
-          ++m_cursor;
-          if (m_limit <= m_cursor)
-          {
-            fill_line_buffer(1); // LCOV_EXCL_LINE
-          }
-          yych = *m_cursor;
-          if (yybm[0 + yych] & 32)
-          {
-            goto basic_json_parser_6;
-          }
-          {
-            continue;
-          }
-        basic_json_parser_9:
-          yyaccept = 0;
-          yych = *(m_marker = ++m_cursor);
-          if (yych <= 0x1F)
-          {
-            goto basic_json_parser_5;
-          }
-          if (yych <= 0x7F)
-          {
-            goto basic_json_parser_31;
-          }
-          if (yych <= 0xC1)
-          {
-            goto basic_json_parser_5;
-          }
-          if (yych <= 0xF4)
-          {
-            goto basic_json_parser_31;
-          }
-          goto basic_json_parser_5;
-        basic_json_parser_10:
-          ++m_cursor;
-          {
-            last_token_type = token_type::value_separator;
-            break;
-          }
-        basic_json_parser_12:
-          yych = *++m_cursor;
-          if (yych <= '/')
-          {
-            goto basic_json_parser_5;
-          }
-          if (yych <= '0')
-          {
-            goto basic_json_parser_13;
-          }
-          if (yych <= '9')
-          {
-            goto basic_json_parser_15;
-          }
-          goto basic_json_parser_5;
-        basic_json_parser_13:
-          yyaccept = 1;
-          yych = *(m_marker = ++m_cursor);
-          if (yych <= 'D')
-          {
-            if (yych == '.')
-            {
-              goto basic_json_parser_43;
-            }
-          }
-          else
-          {
-            if (yych <= 'E')
-            {
-              goto basic_json_parser_44;
-            }
-            if (yych == 'e')
-            {
-              goto basic_json_parser_44;
-            }
-          }
-        basic_json_parser_14:
-        {
-          last_token_type = token_type::value_number;
-          break;
-        }
-        basic_json_parser_15:
-          yyaccept = 1;
-          m_marker = ++m_cursor;
-          if ((m_limit - m_cursor) < 3)
-          {
-            fill_line_buffer(3); // LCOV_EXCL_LINE
-          }
-          yych = *m_cursor;
-          if (yybm[0 + yych] & 64)
-          {
-            goto basic_json_parser_15;
-          }
-          if (yych <= 'D')
-          {
-            if (yych == '.')
-            {
-              goto basic_json_parser_43;
-            }
-            goto basic_json_parser_14;
-          }
-          else
-          {
-            if (yych <= 'E')
-            {
-              goto basic_json_parser_44;
-            }
-            if (yych == 'e')
-            {
-              goto basic_json_parser_44;
-            }
-            goto basic_json_parser_14;
-          }
-        basic_json_parser_17:
-          ++m_cursor;
-          {
-            last_token_type = token_type::name_separator;
-            break;
-          }
-        basic_json_parser_19:
-          ++m_cursor;
-          {
-            last_token_type = token_type::begin_array;
-            break;
-          }
-        basic_json_parser_21:
-          ++m_cursor;
-          {
-            last_token_type = token_type::end_array;
-            break;
-          }
-        basic_json_parser_23:
-          yyaccept = 0;
-          yych = *(m_marker = ++m_cursor);
-          if (yych == 'a')
-          {
-            goto basic_json_parser_45;
-          }
-          goto basic_json_parser_5;
-        basic_json_parser_24:
-          yyaccept = 0;
-          yych = *(m_marker = ++m_cursor);
-          if (yych == 'u')
-          {
-            goto basic_json_parser_46;
-          }
-          goto basic_json_parser_5;
-        basic_json_parser_25:
-          yyaccept = 0;
-          yych = *(m_marker = ++m_cursor);
-          if (yych == 'r')
-          {
-            goto basic_json_parser_47;
-          }
-          goto basic_json_parser_5;
-        basic_json_parser_26:
-          ++m_cursor;
-          {
-            last_token_type = token_type::begin_object;
-            break;
-          }
-        basic_json_parser_28:
-          ++m_cursor;
-          {
-            last_token_type = token_type::end_object;
-            break;
-          }
-        basic_json_parser_30:
-          ++m_cursor;
-          if (m_limit <= m_cursor)
-          {
-            fill_line_buffer(1); // LCOV_EXCL_LINE
-          }
-          yych = *m_cursor;
-        basic_json_parser_31:
-          if (yybm[0 + yych] & 128)
-          {
-            goto basic_json_parser_30;
-          }
-          if (yych <= 0xE0)
-          {
-            if (yych <= '\\')
-            {
-              if (yych <= 0x1F)
-              {
-                goto basic_json_parser_32;
-              }
-              if (yych <= '"')
-              {
-                goto basic_json_parser_33;
-              }
-              goto basic_json_parser_35;
-            }
-            else
-            {
-              if (yych <= 0xC1)
-              {
-                goto basic_json_parser_32;
-              }
-              if (yych <= 0xDF)
-              {
-                goto basic_json_parser_36;
-              }
-              goto basic_json_parser_37;
-            }
-          }
-          else
-          {
-            if (yych <= 0xEF)
-            {
-              if (yych == 0xED)
-              {
-                goto basic_json_parser_39;
-              }
-              goto basic_json_parser_38;
-            }
-            else
+            switch (lhs_type)
             {
-              if (yych <= 0xF0)
-              {
-                goto basic_json_parser_40;
-              }
-              if (yych <= 0xF3)
-              {
-                goto basic_json_parser_41;
-              }
-              if (yych <= 0xF4)
-              {
-                goto basic_json_parser_42;
-              }
-            }
-          }
-        basic_json_parser_32:
-          m_cursor = m_marker;
-          if (yyaccept == 0)
-          {
-            goto basic_json_parser_5;
-          }
-          else
-          {
-            goto basic_json_parser_14;
-          }
-        basic_json_parser_33:
-          ++m_cursor;
-          {
-            last_token_type = token_type::value_string;
-            break;
-          }
-        basic_json_parser_35:
-          ++m_cursor;
-          if (m_limit <= m_cursor)
-          {
-            fill_line_buffer(1); // LCOV_EXCL_LINE
-          }
-          yych = *m_cursor;
-          if (yych <= 'e')
-          {
-            if (yych <= '/')
-            {
-              if (yych == '"')
-              {
-                goto basic_json_parser_30;
-              }
-              if (yych <= '.')
-              {
-                goto basic_json_parser_32;
-              }
-              goto basic_json_parser_30;
-            }
-            else
+            case value_t::array:
             {
-              if (yych <= '\\')
-              {
-                if (yych <= '[')
-                {
-                  goto basic_json_parser_32;
-                }
-                goto basic_json_parser_30;
-              }
-              else
-              {
-                if (yych == 'b')
-                {
-                  goto basic_json_parser_30;
-                }
-                goto basic_json_parser_32;
-              }
-            }
-          }
-          else
-          {
-            if (yych <= 'q')
-            {
-              if (yych <= 'f')
-              {
-                goto basic_json_parser_30;
-              }
-              if (yych == 'n')
-              {
-                goto basic_json_parser_30;
-              }
-              goto basic_json_parser_32;
+                return *lhs.m_value.array < *rhs.m_value.array;
             }
-            else
-            {
-              if (yych <= 's')
-              {
-                if (yych <= 'r')
-                {
-                  goto basic_json_parser_30;
-                }
-                goto basic_json_parser_32;
-              }
-              else
-              {
-                if (yych <= 't')
-                {
-                  goto basic_json_parser_30;
-                }
-                if (yych <= 'u')
-                {
-                  goto basic_json_parser_48;
-                }
-                goto basic_json_parser_32;
-              }
-            }
-          }
-        basic_json_parser_36:
-          ++m_cursor;
-          if (m_limit <= m_cursor)
-          {
-            fill_line_buffer(1); // LCOV_EXCL_LINE
-          }
-          yych = *m_cursor;
-          if (yych <= 0x7F)
-          {
-            goto basic_json_parser_32;
-          }
-          if (yych <= 0xBF)
-          {
-            goto basic_json_parser_30;
-          }
-          goto basic_json_parser_32;
-        basic_json_parser_37:
-          ++m_cursor;
-          if (m_limit <= m_cursor)
-          {
-            fill_line_buffer(1); // LCOV_EXCL_LINE
-          }
-          yych = *m_cursor;
-          if (yych <= 0x9F)
-          {
-            goto basic_json_parser_32;
-          }
-          if (yych <= 0xBF)
-          {
-            goto basic_json_parser_36;
-          }
-          goto basic_json_parser_32;
-        basic_json_parser_38:
-          ++m_cursor;
-          if (m_limit <= m_cursor)
-          {
-            fill_line_buffer(1); // LCOV_EXCL_LINE
-          }
-          yych = *m_cursor;
-          if (yych <= 0x7F)
-          {
-            goto basic_json_parser_32;
-          }
-          if (yych <= 0xBF)
-          {
-            goto basic_json_parser_36;
-          }
-          goto basic_json_parser_32;
-        basic_json_parser_39:
-          ++m_cursor;
-          if (m_limit <= m_cursor)
-          {
-            fill_line_buffer(1); // LCOV_EXCL_LINE
-          }
-          yych = *m_cursor;
-          if (yych <= 0x7F)
-          {
-            goto basic_json_parser_32;
-          }
-          if (yych <= 0x9F)
-          {
-            goto basic_json_parser_36;
-          }
-          goto basic_json_parser_32;
-        basic_json_parser_40:
-          ++m_cursor;
-          if (m_limit <= m_cursor)
-          {
-            fill_line_buffer(1); // LCOV_EXCL_LINE
-          }
-          yych = *m_cursor;
-          if (yych <= 0x8F)
-          {
-            goto basic_json_parser_32;
-          }
-          if (yych <= 0xBF)
-          {
-            goto basic_json_parser_38;
-          }
-          goto basic_json_parser_32;
-        basic_json_parser_41:
-          ++m_cursor;
-          if (m_limit <= m_cursor)
-          {
-            fill_line_buffer(1); // LCOV_EXCL_LINE
-          }
-          yych = *m_cursor;
-          if (yych <= 0x7F)
-          {
-            goto basic_json_parser_32;
-          }
-          if (yych <= 0xBF)
-          {
-            goto basic_json_parser_38;
-          }
-          goto basic_json_parser_32;
-        basic_json_parser_42:
-          ++m_cursor;
-          if (m_limit <= m_cursor)
-          {
-            fill_line_buffer(1); // LCOV_EXCL_LINE
-          }
-          yych = *m_cursor;
-          if (yych <= 0x7F)
-          {
-            goto basic_json_parser_32;
-          }
-          if (yych <= 0x8F)
-          {
-            goto basic_json_parser_38;
-          }
-          goto basic_json_parser_32;
-        basic_json_parser_43:
-          yych = *++m_cursor;
-          if (yych <= '/')
-          {
-            goto basic_json_parser_32;
-          }
-          if (yych <= '9')
-          {
-            goto basic_json_parser_49;
-          }
-          goto basic_json_parser_32;
-        basic_json_parser_44:
-          yych = *++m_cursor;
-          if (yych <= ',')
-          {
-            if (yych == '+')
-            {
-              goto basic_json_parser_51;
-            }
-            goto basic_json_parser_32;
-          }
-          else
-          {
-            if (yych <= '-')
-            {
-              goto basic_json_parser_51;
-            }
-            if (yych <= '/')
-            {
-              goto basic_json_parser_32;
-            }
-            if (yych <= '9')
-            {
-              goto basic_json_parser_52;
-            }
-            goto basic_json_parser_32;
-          }
-        basic_json_parser_45:
-          yych = *++m_cursor;
-          if (yych == 'l')
-          {
-            goto basic_json_parser_54;
-          }
-          goto basic_json_parser_32;
-        basic_json_parser_46:
-          yych = *++m_cursor;
-          if (yych == 'l')
-          {
-            goto basic_json_parser_55;
-          }
-          goto basic_json_parser_32;
-        basic_json_parser_47:
-          yych = *++m_cursor;
-          if (yych == 'u')
-          {
-            goto basic_json_parser_56;
-          }
-          goto basic_json_parser_32;
-        basic_json_parser_48:
-          ++m_cursor;
-          if (m_limit <= m_cursor)
-          {
-            fill_line_buffer(1); // LCOV_EXCL_LINE
-          }
-          yych = *m_cursor;
-          if (yych <= '@')
-          {
-            if (yych <= '/')
-            {
-              goto basic_json_parser_32;
-            }
-            if (yych <= '9')
-            {
-              goto basic_json_parser_57;
-            }
-            goto basic_json_parser_32;
-          }
-          else
-          {
-            if (yych <= 'F')
-            {
-              goto basic_json_parser_57;
-            }
-            if (yych <= '`')
-            {
-              goto basic_json_parser_32;
-            }
-            if (yych <= 'f')
-            {
-              goto basic_json_parser_57;
-            }
-            goto basic_json_parser_32;
-          }
-        basic_json_parser_49:
-          yyaccept = 1;
-          m_marker = ++m_cursor;
-          if ((m_limit - m_cursor) < 3)
-          {
-            fill_line_buffer(3); // LCOV_EXCL_LINE
-          }
-          yych = *m_cursor;
-          if (yych <= 'D')
-          {
-            if (yych <= '/')
-            {
-              goto basic_json_parser_14;
-            }
-            if (yych <= '9')
-            {
-              goto basic_json_parser_49;
-            }
-            goto basic_json_parser_14;
-          }
-          else
-          {
-            if (yych <= 'E')
-            {
-              goto basic_json_parser_44;
-            }
-            if (yych == 'e')
-            {
-              goto basic_json_parser_44;
-            }
-            goto basic_json_parser_14;
-          }
-        basic_json_parser_51:
-          yych = *++m_cursor;
-          if (yych <= '/')
-          {
-            goto basic_json_parser_32;
-          }
-          if (yych >= ':')
-          {
-            goto basic_json_parser_32;
-          }
-        basic_json_parser_52:
-          ++m_cursor;
-          if (m_limit <= m_cursor)
-          {
-            fill_line_buffer(1); // LCOV_EXCL_LINE
-          }
-          yych = *m_cursor;
-          if (yych <= '/')
-          {
-            goto basic_json_parser_14;
-          }
-          if (yych <= '9')
-          {
-            goto basic_json_parser_52;
-          }
-          goto basic_json_parser_14;
-        basic_json_parser_54:
-          yych = *++m_cursor;
-          if (yych == 's')
-          {
-            goto basic_json_parser_58;
-          }
-          goto basic_json_parser_32;
-        basic_json_parser_55:
-          yych = *++m_cursor;
-          if (yych == 'l')
-          {
-            goto basic_json_parser_59;
-          }
-          goto basic_json_parser_32;
-        basic_json_parser_56:
-          yych = *++m_cursor;
-          if (yych == 'e')
-          {
-            goto basic_json_parser_61;
-          }
-          goto basic_json_parser_32;
-        basic_json_parser_57:
-          ++m_cursor;
-          if (m_limit <= m_cursor)
-          {
-            fill_line_buffer(1); // LCOV_EXCL_LINE
-          }
-          yych = *m_cursor;
-          if (yych <= '@')
-          {
-            if (yych <= '/')
-            {
-              goto basic_json_parser_32;
-            }
-            if (yych <= '9')
-            {
-              goto basic_json_parser_63;
-            }
-            goto basic_json_parser_32;
-          }
-          else
-          {
-            if (yych <= 'F')
-            {
-              goto basic_json_parser_63;
-            }
-            if (yych <= '`')
-            {
-              goto basic_json_parser_32;
-            }
-            if (yych <= 'f')
-            {
-              goto basic_json_parser_63;
-            }
-            goto basic_json_parser_32;
-          }
-        basic_json_parser_58:
-          yych = *++m_cursor;
-          if (yych == 'e')
-          {
-            goto basic_json_parser_64;
-          }
-          goto basic_json_parser_32;
-        basic_json_parser_59:
-          ++m_cursor;
-          {
-            last_token_type = token_type::literal_null;
-            break;
-          }
-        basic_json_parser_61:
-          ++m_cursor;
-          {
-            last_token_type = token_type::literal_true;
-            break;
-          }
-        basic_json_parser_63:
-          ++m_cursor;
-          if (m_limit <= m_cursor)
-          {
-            fill_line_buffer(1); // LCOV_EXCL_LINE
-          }
-          yych = *m_cursor;
-          if (yych <= '@')
-          {
-            if (yych <= '/')
+            case value_t::object:
             {
-              goto basic_json_parser_32;
+                return *lhs.m_value.object < *rhs.m_value.object;
             }
-            if (yych <= '9')
+            case value_t::null:
             {
-              goto basic_json_parser_66;
+                return false;
             }
-            goto basic_json_parser_32;
-          }
-          else
-          {
-            if (yych <= 'F')
+            case value_t::string:
             {
-              goto basic_json_parser_66;
+                return *lhs.m_value.string < *rhs.m_value.string;
             }
-            if (yych <= '`')
+            case value_t::boolean:
             {
-              goto basic_json_parser_32;
+                return lhs.m_value.boolean < rhs.m_value.boolean;
             }
-            if (yych <= 'f')
+            case value_t::number_integer:
             {
-              goto basic_json_parser_66;
+                return lhs.m_value.number_integer < rhs.m_value.number_integer;
             }
-            goto basic_json_parser_32;
-          }
-        basic_json_parser_64:
-          ++m_cursor;
-          {
-            last_token_type = token_type::literal_false;
-            break;
-          }
-        basic_json_parser_66:
-          ++m_cursor;
-          if (m_limit <= m_cursor)
-          {
-            fill_line_buffer(1); // LCOV_EXCL_LINE
-          }
-          yych = *m_cursor;
-          if (yych <= '@')
-          {
-            if (yych <= '/')
+            case value_t::number_unsigned:
             {
-              goto basic_json_parser_32;
+                return lhs.m_value.number_unsigned <
+                       rhs.m_value.number_unsigned;
             }
-            if (yych <= '9')
+            case value_t::number_float:
             {
-              goto basic_json_parser_30;
+                return lhs.m_value.number_float < rhs.m_value.number_float;
             }
-            goto basic_json_parser_32;
-          }
-          else
-          {
-            if (yych <= 'F')
+            default:
             {
-              goto basic_json_parser_30;
+                return false;
             }
-            if (yych <= '`')
-            {
-              goto basic_json_parser_32;
-            }
-            if (yych <= 'f')
-            {
-              goto basic_json_parser_30;
             }
-            goto basic_json_parser_32;
-          }
         }
-      }
+        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)
+        {
+            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 last_token_type;
+        // 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 append data from the stream to the line buffer
+    @brief comparison: less than or equal
 
-    This function is called by the scan() function when the end of the
-    buffer (`m_limit`) is reached and the `m_cursor` pointer cannot be
-    incremented without leaving the limits of the line buffer. Note re2c
-    decides when to call this function.
+    Compares whether one JSON value @a lhs is less than or equal to another
+    JSON value by calculating `not (rhs < lhs)`.
 
-    If the lexer reads from contiguous storage, there is no trailing null
-    byte. Therefore, this function must make sure to add these padding
-    null bytes.
+    @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
 
-    If the lexer reads from an input stream, this function reads the next
-    line of the input.
+    @complexity Linear.
 
-    @pre
-        p p p p p p u u u u u x . . . . . .
-        ^           ^       ^   ^
-        m_content   m_start |   m_limit
-                            m_cursor
+    @liveexample{The example demonstrates comparing several JSON
+    types.,operator__greater}
 
-    @post
-        u u u u u x x x x x x x . . . . . .
-        ^       ^               ^
-        |       m_cursor        m_limit
-        m_start
-        m_content
+    @since version 1.0.0
     */
-    void fill_line_buffer(size_t n = 0)
+    friend bool operator<=(const_reference lhs, const_reference rhs) noexcept
     {
-      // if line buffer is used, m_content points to its data
-      assert(m_line_buffer.empty() or
-             m_content ==
-                 reinterpret_cast<const lexer_char_t *>(m_line_buffer.data()));
+        return not(rhs < lhs);
+    }
 
-      // if line buffer is used, m_limit is set past the end of its data
-      assert(m_line_buffer.empty() or
-             m_limit == m_content + m_line_buffer.size());
+    /*!
+    @brief comparison: greater than
 
-      // pointer relationships
-      assert(m_content <= m_start);
-      assert(m_start <= m_cursor);
-      assert(m_cursor <= m_limit);
-      assert(m_marker == nullptr or m_marker <= m_limit);
+    Compares whether one JSON value @a lhs is greater than another
+    JSON value by calculating `not (lhs <= rhs)`.
 
-      // number of processed characters (p)
-      const auto num_processed_chars = static_cast<size_t>(m_start - m_content);
-      // offset for m_marker wrt. to m_start
-      const auto offset_marker = (m_marker == nullptr) ? 0 : m_marker - m_start;
-      // number of unprocessed characters (u)
-      const auto offset_cursor = m_cursor - m_start;
+    @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
 
-      // no stream is used or end of file is reached
-      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
-        // thing. See http://stackoverflow.com/q/28142011/266378
-        m_line_buffer.assign(m_start, m_limit);
+    @complexity Linear.
 
-        // append n characters to make sure that there is sufficient
-        // space between m_cursor and m_limit
-        m_line_buffer.append(1, '\x00');
-        if (n > 0)
-        {
-          m_line_buffer.append(n - 1, '\x01');
-        }
-      }
-      else
-      {
-        // delete processed characters from line buffer
-        m_line_buffer.erase(0, num_processed_chars);
-        // read next line from input stream
-        m_line_buffer_tmp.clear();
-        std::getline(*m_stream, m_line_buffer_tmp, '\n');
+    @liveexample{The example demonstrates comparing several JSON
+    types.,operator__lessequal}
 
-        // add line with newline symbol to the line buffer
-        m_line_buffer += m_line_buffer_tmp;
-        m_line_buffer.push_back('\n');
-      }
+    @since version 1.0.0
+    */
+    friend bool operator>(const_reference lhs, const_reference rhs) noexcept
+    {
+        return not(lhs <= rhs);
+    }
 
-      // set pointers
-      m_content = reinterpret_cast<const lexer_char_t *>(m_line_buffer.data());
-      assert(m_content != nullptr);
-      m_start = m_content;
-      m_marker = m_start + offset_marker;
-      m_cursor = m_start + offset_cursor;
-      m_limit = m_start + m_line_buffer.size();
-    }
-
-    /// return string representation of last read token
-    string_t get_token_string() const
-    {
-      assert(m_start != nullptr);
-      return string_t(
-          reinterpret_cast<typename string_t::const_pointer>(m_start),
-          static_cast<size_t>(m_cursor - m_start));
-    }
-
-    /*!
-    @brief return string value for string tokens
-
-    The function iterates the characters between the opening and closing
-    quotes of the string value. The complete string is the range
-    [m_start,m_cursor). Consequently, we iterate from m_start+1 to
-    m_cursor-1.
-
-    We differentiate two cases:
-
-    1. Escaped characters. In this case, a new character is constructed
-       according to the nature of the escape. Some escapes create new
-       characters (e.g., `"\\n"` is replaced by `"\n"`), some are copied
-       as is (e.g., `"\\\\"`). Furthermore, Unicode escapes of the shape
-       `"\\uxxxx"` need special care. In this case, to_unicode takes care
-       of the construction of the values.
-    2. Unescaped characters are copied as is.
-
-    @pre `m_cursor - m_start >= 2`, meaning the length of the last token
-    is at least 2 bytes which is trivially true for any string (which
-    consists of at least two quotes).
-
-        " c1 c2 c3 ... "
-        ^                ^
-        m_start          m_cursor
-
-    @complexity Linear in the length of the string.\n
-
-    Lemma: The loop body will always terminate.\n
-
-    Proof (by contradiction): Assume the loop body does not terminate. As
-    the loop body does not contain another loop, one of the called
-    functions must never return. The called functions are `std::strtoul`
-    and to_unicode. Neither function can loop forever, so the loop body
-    will never loop forever which contradicts the assumption that the loop
-    body does not terminate, q.e.d.\n
-
-    Lemma: The loop condition for the for loop is eventually false.\n
-
-    Proof (by contradiction): Assume the loop does not terminate. Due to
-    the above lemma, this can only be due to a tautological loop
-    condition; that is, the loop condition i < m_cursor - 1 must always be
-    true. Let x be the change of i for any loop iteration. Then
-    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
-    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
-    contradicts the assumption that the loop condition is a tautology,
-    q.e.d.
-
-    @return string value of current token without opening and closing
-    quotes
-    @throw std::out_of_range if to_unicode fails
-    */
-    string_t get_string() const
-    {
-      assert(m_cursor - m_start >= 2);
-
-      string_t result;
-      result.reserve(static_cast<size_t>(m_cursor - m_start - 2));
-
-      // iterate the result between the quotes
-      for (const lexer_char_t *i = m_start + 1; i < m_cursor - 1; ++i)
-      {
-        // find next escape character
-        auto e = std::find(i, m_cursor - 1, '\\');
-        if (e != i)
-        {
-          // see
-          // https://github.com/nlohmann/json/issues/365#issuecomment-262874705
-          for (auto k = i; k < e; k++)
-          {
-            result.push_back(static_cast<typename string_t::value_type>(*k));
-          }
-          i = e - 1; // -1 because of ++i
-        }
-        else
-        {
-          // processing escaped character
-          // read next character
-          ++i;
+    /*!
+    @brief comparison: greater than or equal
 
-          switch (*i)
-          {
-            // the default escapes
-            case 't':
-            {
-              result += "\t";
-              break;
-            }
-            case 'b':
-            {
-              result += "\b";
-              break;
-            }
-            case 'f':
-            {
-              result += "\f";
-              break;
-            }
-            case 'n':
-            {
-              result += "\n";
-              break;
-            }
-            case 'r':
-            {
-              result += "\r";
-              break;
-            }
-            case '\\':
-            {
-              result += "\\";
-              break;
-            }
-            case '/':
-            {
-              result += "/";
-              break;
-            }
-            case '"':
-            {
-              result += "\"";
-              break;
-            }
+    Compares whether one JSON value @a lhs is greater than or equal to another
+    JSON value by calculating `not (lhs < rhs)`.
 
-            // unicode
-            case 'u':
-            {
-              // get code xxxx from uxxxx
-              auto codepoint = std::strtoul(
-                  std::string(
-                      reinterpret_cast<typename string_t::const_pointer>(i + 1),
-                      4)
-                      .c_str(),
-                  nullptr, 16);
+    @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
 
-              // check if codepoint is a high surrogate
-              if (codepoint >= 0xD800 and codepoint <= 0xDBFF)
-              {
-                // make sure there is a subsequent unicode
-                if ((i + 6 >= m_limit) or *(i + 5) != '\\' or *(i + 6) != 'u')
-                {
-                  JSON_THROW(std::invalid_argument("missing low surrogate"));
-                }
+    @complexity Linear.
 
-                // get code yyyy from uxxxx\uyyyy
-                auto codepoint2 = std::strtoul(
-                    std::string(
-                        reinterpret_cast<typename string_t::const_pointer>(i +
-                                                                           7),
-                        4)
-                        .c_str(),
-                    nullptr, 16);
-                result += to_unicode(codepoint, codepoint2);
-                // skip the next 10 characters (xxxx\uyyyy)
-                i += 10;
-              }
-              else if (codepoint >= 0xDC00 and codepoint <= 0xDFFF)
-              {
-                // we found a lone low surrogate
-                JSON_THROW(std::invalid_argument("missing high surrogate"));
-              }
-              else
-              {
-                // add unicode character(s)
-                result += to_unicode(codepoint);
-                // skip the next four characters (xxxx)
-                i += 4;
-              }
-              break;
-            }
-          }
-        }
-      }
+    @liveexample{The example demonstrates comparing several JSON
+    types.,operator__greaterequal}
 
-      return result;
+    @since version 1.0.0
+    */
+    friend bool operator>=(const_reference lhs, const_reference rhs) noexcept
+    {
+        return not(lhs < rhs);
     }
 
+    /// @}
+
+    ///////////////////
+    // serialization //
+    ///////////////////
+
+    /// @name serialization
+    /// @{
+
     /*!
-    @brief parse floating point number
+    @brief serialize to stream
+
+    Serialize the given JSON value @a j to the output stream @a o. The JSON
+    value will be serialized using the @ref dump member function. The
+    indentation of the output can be controlled with the member variable
+    `width` of the output stream @a o. For instance, using the manipulator
+    `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
+
+    @return the stream @a o
 
-    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).
+    @complexity Linear.
 
-    @param[in,out] endptr recieves a pointer to the first character after
-    the number
+    @liveexample{The example below shows the serialization with different
+    parameters to `width` to adjust the indentation level.,operator_serialize}
 
-    @return the floating point number
+    @since version 1.0.0
     */
-    long double str_to_float_t(long double * /* type */, char **endptr) const
+    friend std::ostream &operator<<(std::ostream &o, const basic_json &j)
     {
-      return std::strtold(
-          reinterpret_cast<typename string_t::const_pointer>(m_start), endptr);
-    }
+        // read width member and use it as indentation parameter if nonzero
+        const bool pretty_print = (o.width() > 0);
+        const auto indentation = (pretty_print ? o.width() : 0);
 
-    /*!
-    @brief parse floating point number
+        // 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);
 
-    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).
+        // do the actual serialization
+        j.dump(o, pretty_print, static_cast<unsigned int>(indentation));
 
-    @param[in,out] endptr  recieves a pointer to the first character after
-    the number
+        // reset locale and precision
+        o.imbue(old_locale);
+        o.precision(old_precision);
+        return o;
+    }
 
-    @return the floating point number
+    /*!
+    @brief serialize to stream
+    @copydoc operator<<(std::ostream&, const basic_json&)
     */
-    double str_to_float_t(double * /* type */, char **endptr) const
+    friend std::ostream &operator>>(const basic_json &j, std::ostream &o)
     {
-      return std::strtod(
-          reinterpret_cast<typename string_t::const_pointer>(m_start), endptr);
+        return o << j;
     }
 
+    /// @}
+
+    /////////////////////
+    // deserialization //
+    /////////////////////
+
+    /// @name deserialization
+    /// @{
+
     /*!
-    @brief parse floating point number
+    @brief deserialize from an array
+
+    This function reads from an array of 1-byte values.
+
+    @pre Each element of the container has a size of 1 byte. Violating this
+    precondition yields undefined behavior. **This precondition is enforced
+    with a static assertion.**
+
+    @param[in] array  array to read 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)
 
-    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).
+    @return result of the deserialization
 
-    @param[in,out] endptr  recieves a pointer to the first character after
-    the number
+    @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.
 
-    @return the floating point number
+    @note A UTF-8 byte order mark is silently ignored.
+
+    @liveexample{The example below demonstrates the `parse()` function reading
+    from an array.,parse__array__parser_callback_t}
+
+    @since version 2.0.3
     */
-    float str_to_float_t(float * /* type */, char **endptr) const
+    template <class T, std::size_t N>
+    static basic_json parse(T (&array)[N], const parser_callback_t cb = nullptr)
     {
-      return std::strtof(
-          reinterpret_cast<typename string_t::const_pointer>(m_start), endptr);
+        // delegate the call to the iterator-range parse overload
+        return parse(std::begin(array), std::end(array), cb);
     }
 
     /*!
-    @brief return number value for number tokens
+    @brief deserialize from string literal
+
+    @tparam CharT character/literal type with size of 1 byte
+    @param[in] s  string literal 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)
+
+    @return result of the deserialization
 
-    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.
+    @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.
 
-    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.
+    @note A UTF-8 byte order mark is silently ignored.
+    @note String containers like `std::string` or @ref string_t can be parsed
+          with @ref parse(const ContiguousContainer&, const parser_callback_t)
 
-    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).
+    @liveexample{The example below demonstrates the `parse()` function with
+    and without callback function.,parse__string__parser_callback_t}
 
-    @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.
+    @sa @ref parse(std::istream&, const parser_callback_t) for a version that
+    reads from an input stream
+
+    @since version 1.0.0 (originally for @ref string_t)
     */
-    void get_number(basic_json &result) const
+    template <typename CharT,
+              typename std::enable_if<
+                  std::is_pointer<CharT>::value and
+                      std::is_integral<
+                          typename std::remove_pointer<CharT>::type>::value and
+                      sizeof(typename std::remove_pointer<CharT>::type) == 1,
+                  int>::type = 0>
+    static basic_json parse(const CharT s, const parser_callback_t cb = nullptr)
     {
-      assert(m_start != nullptr);
-
-      const lexer::lexer_char_t *curptr = m_start;
+        return parser(reinterpret_cast<const char *>(s), cb).parse();
+    }
 
-      // accumulate the integer conversion result (unsigned for now)
-      number_unsigned_t value = 0;
+    /*!
+    @brief deserialize from stream
 
-      // maximum absolute value of the relevant integer type
-      number_unsigned_t max;
+    @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)
 
-      // temporarily store the type to avoid unecessary bitfield access
-      value_t type;
+    @return result of the deserialization
 
-      // look for sign
-      if (*curptr == '-')
-      {
-        type = value_t::number_integer;
-        max = static_cast<uint64_t>(
-                  (std::numeric_limits<number_integer_t>::max)()) +
-              1;
-        curptr++;
-      }
-      else
-      {
-        type = value_t::number_unsigned;
-        max = static_cast<uint64_t>(
-            (std::numeric_limits<number_unsigned_t>::max)());
-      }
+    @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.
 
-      // count the significant figures
-      for (; curptr < m_cursor; curptr++)
-      {
-        // 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;
-        }
+    @note A UTF-8 byte order mark is silently ignored.
 
-        // skip if definitely not an integer
-        if (type != value_t::number_float)
-        {
-          auto digit = static_cast<number_unsigned_t>(*curptr - '0');
+    @liveexample{The example below demonstrates the `parse()` function with
+    and without callback function.,parse__istream__parser_callback_t}
 
-          // 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;
-          }
-        }
-      }
-
-      // save the value (if not a float)
-      if (type == value_t::number_unsigned)
-      {
-        result.m_value.number_unsigned = value;
-      }
-      else if (type == value_t::number_integer)
-      {
-        // 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)
-        {
-          // 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);
-        }
-        else
-        {
-          // all other values can be negated safely
-          result.m_value.number_integer = -static_cast<number_integer_t>(value);
-        }
-      }
-      else
-      {
-        // parse with strtod
-        result.m_value.number_float =
-            str_to_float_t(static_cast<number_float_t *>(nullptr), nullptr);
+    @sa @ref parse(const CharT, const parser_callback_t) for a version
+    that reads from a string
 
-        // replace infinity and NAN by null
-        if (not std::isfinite(result.m_value.number_float))
-        {
-          type = value_t::null;
-          result.m_value = basic_json::json_value();
-        }
-      }
-
-      // save the type
-      result.m_type = type;
-    }
-
-  private:
-    /// optional input stream
-    std::istream *m_stream = nullptr;
-    /// line buffer buffer for m_stream
-    string_t m_line_buffer{};
-    /// used for filling m_line_buffer
-    string_t m_line_buffer_tmp{};
-    /// the buffer pointer
-    const lexer_char_t *m_content = nullptr;
-    /// pointer to the beginning of the current symbol
-    const lexer_char_t *m_start = nullptr;
-    /// pointer for backtracking information
-    const lexer_char_t *m_marker = nullptr;
-    /// pointer to the current symbol
-    const lexer_char_t *m_cursor = nullptr;
-    /// pointer to the end of the buffer
-    const lexer_char_t *m_limit = nullptr;
-    /// the last token type
-    token_type last_token_type = token_type::end_of_input;
-  };
-
-  /*!
-  @brief syntax analysis
-
-  This class implements a recursive decent parser.
-  */
-  class parser
-  {
-  public:
-    /// a parser reading from a string literal
-    parser(const char *buff, const parser_callback_t cb = nullptr)
-        : callback(cb),
-          m_lexer(reinterpret_cast<const typename lexer::lexer_char_t *>(buff),
-                  std::strlen(buff))
+    @since version 1.0.0
+    */
+    static basic_json parse(std::istream &i,
+                            const parser_callback_t cb = nullptr)
     {
+        return parser(i, cb).parse();
     }
 
-    /// a parser reading from an input stream
-    parser(std::istream &is, const parser_callback_t cb = nullptr)
-        : callback(cb), m_lexer(is)
+    /*!
+    @copydoc parse(std::istream&, const parser_callback_t)
+    */
+    static basic_json parse(std::istream &&i,
+                            const parser_callback_t cb = nullptr)
     {
+        return parser(i, cb).parse();
     }
 
-    /// a parser reading from an iterator range with contiguous storage
-    template <class IteratorType,
-              typename std::enable_if<
-                  std::is_same<typename std::iterator_traits<
-                                   IteratorType>::iterator_category,
-                               std::random_access_iterator_tag>::value,
-                  int>::type = 0>
-    parser(IteratorType first, IteratorType last,
-           const parser_callback_t cb = nullptr)
-        : callback(cb),
-          m_lexer(
-              reinterpret_cast<const typename lexer::lexer_char_t *>(&(*first)),
-              static_cast<size_t>(std::distance(first, last)))
-    {
-    }
-
-    /// public parser interface
-    basic_json parse()
-    {
-      // read first token
-      get_token();
-
-      basic_json result = parse_internal(true);
-      result.assert_invariant();
+    /*!
+    @brief deserialize from an iterator range with contiguous storage
 
-      expect(lexer::token_type::end_of_input);
+    This function reads from an iterator range of a container with contiguous
+    storage of 1-byte values. Compatible container types include
+    `std::vector`, `std::string`, `std::array`, `std::valarray`, and
+    `std::initializer_list`. Furthermore, C-style arrays can be used with
+    `std::begin()`/`std::end()`. User-defined containers can be used as long
+    as they implement random-access iterators and a contiguous storage.
 
-      // return parser result and replace it with null in case the
-      // top-level value was discarded by the callback function
-      return result.is_discarded() ? basic_json() : std::move(result);
-    }
+    @pre The iterator range is contiguous. Violating this precondition yields
+    undefined behavior. **This precondition is enforced with an assertion.**
+    @pre Each element in the range has a size of 1 byte. Violating this
+    precondition yields undefined behavior. **This precondition is enforced
+    with a static assertion.**
 
-  private:
-    /// the actual parser
-    basic_json parse_internal(bool keep)
-    {
-      auto result = basic_json(value_t::discarded);
+    @warning There is no way to enforce all preconditions at compile-time. If
+             the function is called with noncompliant iterators and with
+             assertions switched off, the behavior is undefined and will most
+             likely yield segmentation violation.
 
-      switch (last_token)
-      {
-        case lexer::token_type::begin_object:
-        {
-          if (keep and (not callback or
-                        ((keep = callback(depth++, parse_event_t::object_start,
-                                          result)) != 0)))
-          {
-            // explicitly set result to object to cope with {}
-            result.m_type = value_t::object;
-            result.m_value = value_t::object;
-          }
+    @tparam IteratorType iterator of container with contiguous storage
+    @param[in] first  begin of the range to parse (included)
+    @param[in] last  end of the range to parse (excluded)
+    @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)
 
-          // read next token
-          get_token();
+    @return result of the deserialization
 
-          // closing } -> we are done
-          if (last_token == lexer::token_type::end_object)
-          {
-            get_token();
-            if (keep and callback and
-                not callback(--depth, parse_event_t::object_end, result))
-            {
-              result = basic_json(value_t::discarded);
-            }
-            return result;
-          }
+    @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.
 
-          // no comma is expected here
-          unexpect(lexer::token_type::value_separator);
+    @note A UTF-8 byte order mark is silently ignored.
 
-          // otherwise: parse key-value pairs
-          do
-          {
-            // ugly, but could be fixed with loop reorganization
-            if (last_token == lexer::token_type::value_separator)
-            {
-              get_token();
-            }
+    @liveexample{The example below demonstrates the `parse()` function reading
+    from an iterator range.,parse__iteratortype__parser_callback_t}
 
-            // store key
-            expect(lexer::token_type::value_string);
-            const auto key = m_lexer.get_string();
+    @since version 2.0.3
+    */
+    template <class IteratorType,
+              typename std::enable_if<
+                  std::is_base_of<std::random_access_iterator_tag,
+                                  typename std::iterator_traits<
+                                      IteratorType>::iterator_category>::value,
+                  int>::type = 0>
+    static basic_json parse(IteratorType first, IteratorType last,
+                            const parser_callback_t cb = nullptr)
+    {
+        // assertion to check that the iterator range is indeed contiguous,
+        // see http://stackoverflow.com/a/35008842/266378 for more discussion
+        assert(std::accumulate(
+                   first, last, std::pair<bool, int>(true, 0),
+                   [&first](std::pair<bool, int> res, decltype(*first) val) {
+                       res.first &=
+                           (val ==
+                            *(std::next(std::addressof(*first), res.second++)));
+                       return res;
+                   })
+                   .first);
 
-            bool keep_tag = false;
-            if (keep)
-            {
-              if (callback)
-              {
-                basic_json k(key);
-                keep_tag = callback(depth, parse_event_t::key, k);
-              }
-              else
-              {
-                keep_tag = true;
-              }
-            }
+        // assertion to check that each element is 1 byte long
+        static_assert(
+            sizeof(typename std::iterator_traits<IteratorType>::value_type) ==
+                1,
+            "each element in the iterator range must have the size of 1 byte");
 
-            // parse separator (:)
-            get_token();
-            expect(lexer::token_type::name_separator);
+        // if iterator range is empty, create a parser with an empty string
+        // to generate "unexpected EOF" error message
+        if (std::distance(first, last) <= 0)
+        {
+            return parser("").parse();
+        }
 
-            // parse and add value
-            get_token();
-            auto value = parse_internal(keep);
-            if (keep and keep_tag and not value.is_discarded())
-            {
-              result[key] = std::move(value);
-            }
-          } while (last_token == lexer::token_type::value_separator);
+        return parser(first, last, cb).parse();
+    }
 
-          // closing }
-          expect(lexer::token_type::end_object);
-          get_token();
-          if (keep and callback and
-              not callback(--depth, parse_event_t::object_end, result))
-          {
-            result = basic_json(value_t::discarded);
-          }
+    /*!
+    @brief deserialize from a container with contiguous storage
 
-          return result;
-        }
+    This function reads from a container with contiguous storage of 1-byte
+    values. Compatible container types include `std::vector`, `std::string`,
+    `std::array`, and `std::initializer_list`. User-defined containers can be
+    used as long as they implement random-access iterators and a contiguous
+    storage.
 
-        case lexer::token_type::begin_array:
-        {
-          if (keep and (not callback or
-                        ((keep = callback(depth++, parse_event_t::array_start,
-                                          result)) != 0)))
-          {
-            // explicitly set result to object to cope with []
-            result.m_type = value_t::array;
-            result.m_value = value_t::array;
-          }
+    @pre The container storage is contiguous. Violating this precondition
+    yields undefined behavior. **This precondition is enforced with an
+    assertion.**
+    @pre Each element of the container has a size of 1 byte. Violating this
+    precondition yields undefined behavior. **This precondition is enforced
+    with a static assertion.**
 
-          // read next token
-          get_token();
+    @warning There is no way to enforce all preconditions at compile-time. If
+             the function is called with a noncompliant container and with
+             assertions switched off, the behavior is undefined and will most
+             likely yield segmentation violation.
 
-          // closing ] -> we are done
-          if (last_token == lexer::token_type::end_array)
-          {
-            get_token();
-            if (callback and
-                not callback(--depth, parse_event_t::array_end, result))
-            {
-              result = basic_json(value_t::discarded);
-            }
-            return result;
-          }
+    @tparam ContiguousContainer container type with contiguous storage
+    @param[in] c  container to read 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)
 
-          // no comma is expected here
-          unexpect(lexer::token_type::value_separator);
+    @return result of the deserialization
 
-          // otherwise: parse values
-          do
-          {
-            // ugly, but could be fixed with loop reorganization
-            if (last_token == lexer::token_type::value_separator)
-            {
-              get_token();
-            }
+    @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.
 
-            // parse value
-            auto value = parse_internal(keep);
-            if (keep and not value.is_discarded())
-            {
-              result.push_back(std::move(value));
-            }
-          } while (last_token == lexer::token_type::value_separator);
+    @note A UTF-8 byte order mark is silently ignored.
 
-          // closing ]
-          expect(lexer::token_type::end_array);
-          get_token();
-          if (keep and callback and
-              not callback(--depth, parse_event_t::array_end, result))
-          {
-            result = basic_json(value_t::discarded);
-          }
+    @liveexample{The example below demonstrates the `parse()` function reading
+    from a contiguous container.,parse__contiguouscontainer__parser_callback_t}
 
-          return result;
-        }
+    @since version 2.0.3
+    */
+    template <class ContiguousContainer,
+              typename std::enable_if<
+                  not std::is_pointer<ContiguousContainer>::value and
+                      std::is_base_of<
+                          std::random_access_iterator_tag,
+                          typename std::iterator_traits<decltype(std::begin(
+                              std::declval<ContiguousContainer const>()))>::
+                              iterator_category>::value,
+                  int>::type = 0>
+    static basic_json parse(const ContiguousContainer &c,
+                            const parser_callback_t cb = nullptr)
+    {
+        // delegate the call to the iterator-range parse overload
+        return parse(std::begin(c), std::end(c), cb);
+    }
 
-        case lexer::token_type::literal_null:
-        {
-          get_token();
-          result.m_type = value_t::null;
-          break;
-        }
+    /*!
+    @brief deserialize from stream
 
-        case lexer::token_type::value_string:
-        {
-          const auto s = m_lexer.get_string();
-          get_token();
-          result = basic_json(s);
-          break;
-        }
+    Deserializes an input stream to a JSON value.
 
-        case lexer::token_type::literal_true:
-        {
-          get_token();
-          result.m_type = value_t::boolean;
-          result.m_value = true;
-          break;
-        }
+    @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
 
-        case lexer::token_type::literal_false:
-        {
-          get_token();
-          result.m_type = value_t::boolean;
-          result.m_value = false;
-          break;
-        }
+    @throw std::invalid_argument in case of parse errors
 
-        case lexer::token_type::value_number:
-        {
-          m_lexer.get_number(result);
-          get_token();
-          break;
-        }
+    @complexity Linear in the length of the input. The parser is a predictive
+    LL(1) parser.
 
-        default:
-        {
-          // the last token was unexpected
-          unexpect(last_token);
-        }
-      }
+    @note A UTF-8 byte order mark is silently ignored.
 
-      if (keep and callback and
-          not callback(depth, parse_event_t::value, result))
-      {
-        result = basic_json(value_t::discarded);
-      }
-      return result;
-    }
+    @liveexample{The example below shows how a JSON value is constructed by
+    reading a serialization from a stream.,operator_deserialize}
 
-    /// get next token from lexer
-    typename lexer::token_type get_token()
-    {
-      last_token = m_lexer.scan();
-      return last_token;
-    }
+    @sa parse(std::istream&, const parser_callback_t) for a variant with a
+    parser callback function to filter values while parsing
 
-    void expect(typename lexer::token_type t) const
+    @since version 1.0.0
+    */
+    friend std::istream &operator<<(basic_json &j, std::istream &i)
     {
-      if (t != last_token)
-      {
-        std::string error_msg = "parse error - unexpected ";
-        error_msg += (last_token == lexer::token_type::parse_error
-                          ? ("'" + 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));
-      }
+        j = parser(i).parse();
+        return i;
     }
 
-    void unexpect(typename lexer::token_type t) const
+    /*!
+    @brief deserialize from stream
+    @copydoc operator<<(basic_json&, std::istream&)
+    */
+    friend std::istream &operator>>(std::istream &i, basic_json &j)
     {
-      if (t == last_token)
-      {
-        std::string error_msg = "parse error - unexpected ";
-        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));
-      }
+        j = parser(i).parse();
+        return i;
     }
 
-  private:
-    /// current level of recursion
-    int depth = 0;
-    /// callback function
-    const parser_callback_t callback = nullptr;
-    /// the type of the last read token
-    typename lexer::token_type last_token = lexer::token_type::uninitialized;
-    /// the lexer
-    lexer m_lexer;
-  };
-
-public:
-  /*!
-  @brief JSON Pointer
-
-  A JSON pointer defines a string syntax for identifying a specific value
-  within a JSON document. It can be used with functions `at` and
-  `operator[]`. Furthermore, JSON pointers are the base for JSON patches.
+    /// @}
 
-  @sa [RFC 6901](https://tools.ietf.org/html/rfc6901)
+    //////////////////////////////////////////
+    // binary serialization/deserialization //
+    //////////////////////////////////////////
 
-  @since version 2.0.0
-  */
-  class json_pointer
-  {
-    /// allow basic_json to access private members
-    friend class basic_json;
-
-  public:
-    /*!
-    @brief create JSON pointer
+    /// @name binary serialization/deserialization support
+    /// @{
 
-    Create a JSON pointer according to the syntax described in
-    [Section 3 of RFC6901](https://tools.ietf.org/html/rfc6901#section-3).
+private:
+    template <typename T>
+    static void add_to_vector(std::vector<uint8_t> &vec, size_t bytes,
+                              const T number)
+    {
+        assert(bytes == 1 or bytes == 2 or bytes == 4 or bytes == 8);
 
-    @param[in] s  string representing the JSON pointer; if omitted, the
-                  empty string is assumed which references the whole JSON
-                  value
+        switch (bytes)
+        {
+        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
+        }
 
-    @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"`
+        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
+        }
 
-    @liveexample{The example shows the construction several valid JSON
-    pointers as well as the exceptional behavior.,json_pointer}
+        case 2:
+        {
+            vec.push_back(static_cast<uint8_t>((number >> 010) & 0xff));
+            // intentional fall-through
+        }
 
-    @since version 2.0.0
-    */
-    explicit json_pointer(const std::string &s = "")
-        : reference_tokens(split(s))
-    {
+        case 1:
+        {
+            vec.push_back(static_cast<uint8_t>(number & 0xff));
+            break;
+        }
+        }
     }
 
     /*!
-    @brief return a string representation of the JSON pointer
+    @brief take sufficient bytes from a vector to fill an integer variable
 
-    @invariant For each JSON pointer `ptr`, it holds:
-    @code {.cpp}
-    ptr == json_pointer(ptr.to_string());
-    @endcode
+    In the context of binary serialization formats, we need to read several
+    bytes from a byte vector and combine them to multi-byte integral data
+    types.
 
-    @return a string representation of the JSON pointer
+    @param[in] vec  byte vector to read from
+    @param[in] current_index  the position in the vector after which to read
 
-    @liveexample{The example shows the result of `to_string`.,
-    json_pointer__to_string}
+    @return the next sizeof(T) bytes from @a vec, in reverse order as T
 
-    @since version 2.0.0
-    */
-    std::string to_string() const noexcept
-    {
-      return std::accumulate(reference_tokens.begin(), reference_tokens.end(),
-                             std::string{},
-                             [](const std::string &a, const std::string &b) {
-                               return a + "/" + escape(b);
-                             });
-    }
+    @tparam T the integral return type
 
-    /// @copydoc to_string()
-    operator std::string() const { return to_string(); }
+    @throw std::out_of_range if there are less than sizeof(T)+1 bytes in the
+           vector @a vec to read
 
-  private:
-    /// remove and return last reference pointer
-    std::string pop_back()
-    {
-      if (is_root())
-      {
-        JSON_THROW(std::domain_error("JSON pointer has no parent"));
-      }
+    In the for loop, the bytes from the vector are copied in reverse order into
+    the return value. In the figures below, let sizeof(T)=4 and `i` be the loop
+    variable.
 
-      auto last = reference_tokens.back();
-      reference_tokens.pop_back();
-      return last;
-    }
+    Precondition:
+
+    vec:   |   |   | a | b | c | d |      T: |   |   |   |   |
+                 ^               ^             ^                ^
+           current_index         i            ptr        sizeof(T)
 
-    /// return whether pointer points to the root document
-    bool is_root() const { return reference_tokens.empty(); }
+    Postcondition:
 
-    json_pointer top() const
+    vec:   |   |   | a | b | c | d |      T: | d | c | b | a |
+                 ^   ^                                     ^
+                 |   i                                    ptr
+           current_index
+
+    @sa Code adapted from <http://stackoverflow.com/a/41031865/266378>.
+    */
+    template <typename T>
+    static T get_from_vector(const std::vector<uint8_t> &vec,
+                             const size_t current_index)
     {
-      if (is_root())
-      {
-        JSON_THROW(std::domain_error("JSON pointer has no parent"));
-      }
+        if (current_index + sizeof(T) + 1 > vec.size())
+        {
+            JSON_THROW(std::out_of_range("cannot read " +
+                                         std::to_string(sizeof(T)) +
+                                         " bytes from vector"));
+        }
 
-      json_pointer result = *this;
-      result.reference_tokens = {reference_tokens[0]};
-      return result;
+        T result;
+        auto *ptr = reinterpret_cast<uint8_t *>(&result);
+        for (size_t i = 0; i < sizeof(T); ++i)
+        {
+            *ptr++ = vec[current_index + sizeof(T) - i];
+        }
+        return result;
     }
 
     /*!
-    @brief create and return a reference to the pointed to value
+    @brief create a MessagePack serialization of a given JSON value
+
+    This is a straightforward implementation of the MessagePack specification.
 
-    @complexity Linear in the number of reference tokens.
+    @param[in] j  JSON value to serialize
+    @param[in,out] v  byte vector to write the serialization to
+
+    @sa https://github.com/msgpack/msgpack/blob/master/spec.md
     */
-    reference get_and_create(reference j) const
+    static void to_msgpack_internal(const basic_json &j,
+                                    std::vector<uint8_t> &v)
     {
-      pointer result = &j;
+        switch (j.type())
+        {
+        case value_t::null:
+        {
+            // nil
+            v.push_back(0xc0);
+            break;
+        }
+
+        case value_t::boolean:
+        {
+            // true and false
+            v.push_back(j.m_value.boolean ? 0xc3 : 0xc2);
+            break;
+        }
 
-      // in case no reference tokens exist, return a reference to the
-      // JSON value j which will be overwritten by a primitive value
-      for (const auto &reference_token : reference_tokens)
-      {
-        switch (result->m_type)
+        case value_t::number_integer:
         {
-          case value_t::null:
-          {
-            if (reference_token == "0")
+            if (j.m_value.number_integer >= 0)
             {
-              // start a new array if reference token is 0
-              result = &result->operator[](0);
+                // MessagePack does not differentiate between positive
+                // signed integers and unsigned integers. Therefore, we
+                // used the code from the value_t::number_unsigned case
+                // here.
+                if (j.m_value.number_unsigned < 128)
+                {
+                    // positive fixnum
+                    add_to_vector(v, 1, j.m_value.number_unsigned);
+                }
+                else if (j.m_value.number_unsigned <= UINT8_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)
+                {
+                    // 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)
+                {
+                    // 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)
+                {
+                    // uint 64
+                    v.push_back(0xcf);
+                    add_to_vector(v, 8, j.m_value.number_unsigned);
+                }
             }
             else
             {
-              // start a new object otherwise
-              result = &result->operator[](reference_token);
+                if (j.m_value.number_integer >= -32)
+                {
+                    // 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)
+                {
+                    // 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)
+                {
+                    // 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)
+                {
+                    // 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)
+                {
+                    // int 64
+                    v.push_back(0xd3);
+                    add_to_vector(v, 8, j.m_value.number_integer);
+                }
             }
             break;
-          }
+        }
 
-          case value_t::object:
-          {
-            // create an entry in the object
-            result = &result->operator[](reference_token);
+        case value_t::number_unsigned:
+        {
+            if (j.m_value.number_unsigned < 128)
+            {
+                // positive fixnum
+                add_to_vector(v, 1, j.m_value.number_unsigned);
+            }
+            else if (j.m_value.number_unsigned <= UINT8_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)
+            {
+                // 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)
+            {
+                // 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)
+            {
+                // uint 64
+                v.push_back(0xcf);
+                add_to_vector(v, 8, j.m_value.number_unsigned);
+            }
             break;
-          }
+        }
 
-          case value_t::array:
-          {
-            // create an entry in the array
-            result = &result->operator[](
-                static_cast<size_type>(std::stoi(reference_token)));
+        case value_t::number_float:
+        {
+            // float 64
+            v.push_back(0xcb);
+            const auto *helper =
+                reinterpret_cast<const uint8_t *>(&(j.m_value.number_float));
+            for (size_t i = 0; i < 8; ++i)
+            {
+                v.push_back(helper[7 - i]);
+            }
             break;
-          }
-
-          /*
-          The following code is only reached if there exists a
-          reference token _and_ the current value is primitive. In
-          this case, we have an error situation, because primitive
-          values may only occur as single value; that is, with an
-          empty list of reference tokens.
-          */
-          default:
-          {
-            JSON_THROW(std::domain_error("invalid value to unflatten"));
-          }
         }
-      }
-
-      return *result;
-    }
-
-    /*!
-    @brief return a reference to the pointed to value
-
-    @note This version does not throw if a value is not present, but tries
-    to create nested values instead. For instance, calling this function
-    with pointer `"/this/that"` on a null value is equivalent to calling
-    `operator[]("this").operator[]("that")` on that value, effectively
-    changing the null value to an object.
-
-    @param[in] ptr  a JSON value
-
-    @return reference to the JSON value pointed to by the JSON pointer
 
-    @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
-    */
-    reference get_unchecked(pointer ptr) const
-    {
-      for (const auto &reference_token : reference_tokens)
-      {
-        // convert null values to arrays or objects before continuing
-        if (ptr->m_type == value_t::null)
+        case value_t::string:
         {
-          // 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 auto N = j.m_value.string->size();
+            if (N <= 31)
+            {
+                // fixstr
+                v.push_back(static_cast<uint8_t>(0xa0 | N));
+            }
+            else if (N <= 255)
+            {
+                // str 8
+                v.push_back(0xd9);
+                add_to_vector(v, 1, N);
+            }
+            else if (N <= 65535)
+            {
+                // str 16
+                v.push_back(0xda);
+                add_to_vector(v, 2, N);
+            }
+            else if (N <= 4294967295)
+            {
+                // str 32
+                v.push_back(0xdb);
+                add_to_vector(v, 4, N);
+            }
 
-          // change value to array for numbers or "-" or to object
-          // otherwise
-          if (nums or reference_token == "-")
-          {
-            *ptr = value_t::array;
-          }
-          else
-          {
-            *ptr = value_t::object;
-          }
+            // append string
+            std::copy(j.m_value.string->begin(), j.m_value.string->end(),
+                      std::back_inserter(v));
+            break;
         }
 
-        switch (ptr->m_type)
+        case value_t::array:
         {
-          case value_t::object:
-          {
-            // use unchecked object access
-            ptr = &ptr->operator[](reference_token);
-            break;
-          }
-
-          case value_t::array:
-          {
-            // error condition (cf. RFC 6901, Sect. 4)
-            if (reference_token.size() > 1 and reference_token[0] == '0')
+            const auto N = j.m_value.array->size();
+            if (N <= 15)
             {
-              JSON_THROW(
-                  std::domain_error("array index must not begin with '0'"));
+                // fixarray
+                v.push_back(static_cast<uint8_t>(0x90 | N));
             }
-
-            if (reference_token == "-")
+            else if (N <= 0xffff)
             {
-              // explicityly treat "-" as index beyond the end
-              ptr = &ptr->operator[](ptr->m_value.array->size());
+                // array 16
+                v.push_back(0xdc);
+                add_to_vector(v, 2, N);
             }
-            else
+            else if (N <= 0xffffffff)
             {
-              // convert array index to number; unchecked access
-              ptr = &ptr->operator[](
-                  static_cast<size_type>(std::stoi(reference_token)));
+                // array 32
+                v.push_back(0xdd);
+                add_to_vector(v, 4, N);
             }
-            break;
-          }
 
-          default:
-          {
-            JSON_THROW(std::out_of_range("unresolved reference token '" +
-                                         reference_token + "'"));
-          }
+            // append each element
+            for (const auto &el : *j.m_value.array)
+            {
+                to_msgpack_internal(el, v);
+            }
+            break;
         }
-      }
-
-      return *ptr;
-    }
 
-    reference get_checked(pointer ptr) const
-    {
-      for (const auto &reference_token : reference_tokens)
-      {
-        switch (ptr->m_type)
+        case value_t::object:
         {
-          case value_t::object:
-          {
-            // note: at performs range check
-            ptr = &ptr->at(reference_token);
-            break;
-          }
-
-          case value_t::array:
-          {
-            if (reference_token == "-")
+            const auto N = j.m_value.object->size();
+            if (N <= 15)
             {
-              // "-" always fails the range check
-              throw std::out_of_range(
-                  "array index '-' (" +
-                  std::to_string(ptr->m_value.array->size()) +
-                  ") is out of range");
+                // fixmap
+                v.push_back(static_cast<uint8_t>(0x80 | (N & 0xf)));
             }
-
-            // error condition (cf. RFC 6901, Sect. 4)
-            if (reference_token.size() > 1 and reference_token[0] == '0')
+            else if (N <= 65535)
             {
-              JSON_THROW(
-                  std::domain_error("array index must not begin with '0'"));
+                // map 16
+                v.push_back(0xde);
+                add_to_vector(v, 2, N);
+            }
+            else if (N <= 4294967295)
+            {
+                // map 32
+                v.push_back(0xdf);
+                add_to_vector(v, 4, N);
             }
 
-            // note: at performs range check
-            ptr = &ptr->at(static_cast<size_type>(std::stoi(reference_token)));
+            // append each element
+            for (const auto &el : *j.m_value.object)
+            {
+                to_msgpack_internal(el.first, v);
+                to_msgpack_internal(el.second, v);
+            }
             break;
-          }
-
-          default:
-          {
-            JSON_THROW(std::out_of_range("unresolved reference token '" +
-                                         reference_token + "'"));
-          }
         }
-      }
 
-      return *ptr;
+        default:
+        {
+            break;
+        }
+        }
     }
 
     /*!
-    @brief return a const reference to the pointed to value
+    @brief create a CBOR serialization of a given JSON value
 
-    @param[in] ptr  a JSON value
+    This is a straightforward implementation of the CBOR specification.
 
-    @return const reference to the JSON value pointed to by the JSON
-            pointer
+    @param[in] j  JSON value to serialize
+    @param[in,out] v  byte vector to write the serialization to
+
+    @sa https://tools.ietf.org/html/rfc7049
     */
-    const_reference get_unchecked(const_pointer ptr) const
+    static void to_cbor_internal(const basic_json &j, std::vector<uint8_t> &v)
     {
-      for (const auto &reference_token : reference_tokens)
-      {
-        switch (ptr->m_type)
+        switch (j.type())
+        {
+        case value_t::null:
+        {
+            v.push_back(0xf6);
+            break;
+        }
+
+        case value_t::boolean:
         {
-          case value_t::object:
-          {
-            // use unchecked object access
-            ptr = &ptr->operator[](reference_token);
+            v.push_back(j.m_value.boolean ? 0xf5 : 0xf4);
             break;
-          }
+        }
 
-          case value_t::array:
-          {
-            if (reference_token == "-")
+        case value_t::number_integer:
+        {
+            if (j.m_value.number_integer >= 0)
             {
-              // "-" cannot be used for const access
-              throw std::out_of_range(
-                  "array index '-' (" +
-                  std::to_string(ptr->m_value.array->size()) +
-                  ") is out of range");
+                // CBOR does not differentiate between positive signed
+                // integers and unsigned integers. Therefore, we used the
+                // code from the value_t::number_unsigned case here.
+                if (j.m_value.number_integer <= 0x17)
+                {
+                    add_to_vector(v, 1, j.m_value.number_integer);
+                }
+                else if (j.m_value.number_integer <= UINT8_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)
+                {
+                    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)
+                {
+                    v.push_back(0x1a);
+                    // four-byte uint32_t
+                    add_to_vector(v, 4, j.m_value.number_integer);
+                }
+                else
+                {
+                    v.push_back(0x1b);
+                    // eight-byte uint64_t
+                    add_to_vector(v, 8, j.m_value.number_integer);
+                }
             }
-
-            // error condition (cf. RFC 6901, Sect. 4)
-            if (reference_token.size() > 1 and reference_token[0] == '0')
+            else
             {
-              JSON_THROW(
-                  std::domain_error("array index must not begin with '0'"));
+                // The conversions below encode the sign in the first
+                // byte, and the value is converted to a positive number.
+                const auto positive_number = -1 - j.m_value.number_integer;
+                if (j.m_value.number_integer >= -24)
+                {
+                    v.push_back(static_cast<uint8_t>(0x20 + positive_number));
+                }
+                else if (positive_number <= UINT8_MAX)
+                {
+                    // int 8
+                    v.push_back(0x38);
+                    add_to_vector(v, 1, positive_number);
+                }
+                else if (positive_number <= UINT16_MAX)
+                {
+                    // int 16
+                    v.push_back(0x39);
+                    add_to_vector(v, 2, positive_number);
+                }
+                else if (positive_number <= UINT32_MAX)
+                {
+                    // int 32
+                    v.push_back(0x3a);
+                    add_to_vector(v, 4, positive_number);
+                }
+                else
+                {
+                    // int 64
+                    v.push_back(0x3b);
+                    add_to_vector(v, 8, positive_number);
+                }
             }
-
-            // use unchecked array access
-            ptr = &ptr->operator[](
-                static_cast<size_type>(std::stoi(reference_token)));
             break;
-          }
-
-          default:
-          {
-            JSON_THROW(std::out_of_range("unresolved reference token '" +
-                                         reference_token + "'"));
-          }
         }
-      }
-
-      return *ptr;
-    }
 
-    const_reference get_checked(const_pointer ptr) const
-    {
-      for (const auto &reference_token : reference_tokens)
-      {
-        switch (ptr->m_type)
+        case value_t::number_unsigned:
         {
-          case value_t::object:
-          {
-            // note: at performs range check
-            ptr = &ptr->at(reference_token);
-            break;
-          }
-
-          case value_t::array:
-          {
-            if (reference_token == "-")
+            if (j.m_value.number_unsigned <= 0x17)
             {
-              // "-" always fails the range check
-              throw std::out_of_range(
-                  "array index '-' (" +
-                  std::to_string(ptr->m_value.array->size()) +
-                  ") is out of range");
+                v.push_back(static_cast<uint8_t>(j.m_value.number_unsigned));
             }
-
-            // error condition (cf. RFC 6901, Sect. 4)
-            if (reference_token.size() > 1 and reference_token[0] == '0')
+            else if (j.m_value.number_unsigned <= 0xff)
             {
-              JSON_THROW(
-                  std::domain_error("array index must not begin with '0'"));
+                v.push_back(0x18);
+                // one-byte uint8_t
+                add_to_vector(v, 1, j.m_value.number_unsigned);
             }
+            else if (j.m_value.number_unsigned <= 0xffff)
+            {
+                v.push_back(0x19);
+                // two-byte uint16_t
+                add_to_vector(v, 2, j.m_value.number_unsigned);
+            }
+            else if (j.m_value.number_unsigned <= 0xffffffff)
+            {
+                v.push_back(0x1a);
+                // four-byte uint32_t
+                add_to_vector(v, 4, j.m_value.number_unsigned);
+            }
+            else if (j.m_value.number_unsigned <= 0xffffffffffffffff)
+            {
+                v.push_back(0x1b);
+                // eight-byte uint64_t
+                add_to_vector(v, 8, j.m_value.number_unsigned);
+            }
+            break;
+        }
+
+        case value_t::number_float:
+        {
+            // Double-Precision Float
+            v.push_back(0xfb);
+            const auto *helper =
+                reinterpret_cast<const uint8_t *>(&(j.m_value.number_float));
+            for (size_t i = 0; i < 8; ++i)
+            {
+                v.push_back(helper[7 - i]);
+            }
+            break;
+        }
+
+        case value_t::string:
+        {
+            const auto N = j.m_value.string->size();
+            if (N <= 0x17)
+            {
+                v.push_back(0x60 + N); // 1 byte for string + size
+            }
+            else if (N <= 0xff)
+            {
+                v.push_back(0x78); // one-byte uint8_t for N
+                add_to_vector(v, 1, N);
+            }
+            else if (N <= 0xffff)
+            {
+                v.push_back(0x79); // two-byte uint16_t for N
+                add_to_vector(v, 2, N);
+            }
+            else if (N <= 0xffffffff)
+            {
+                v.push_back(0x7a); // four-byte uint32_t for N
+                add_to_vector(v, 4, N);
+            }
+            // LCOV_EXCL_START
+            else if (N <= 0xffffffffffffffff)
+            {
+                v.push_back(0x7b); // eight-byte uint64_t for N
+                add_to_vector(v, 8, N);
+            }
+            // LCOV_EXCL_STOP
+
+            // append string
+            std::copy(j.m_value.string->begin(), j.m_value.string->end(),
+                      std::back_inserter(v));
+            break;
+        }
+
+        case value_t::array:
+        {
+            const auto N = j.m_value.array->size();
+            if (N <= 0x17)
+            {
+                v.push_back(0x80 + N); // 1 byte for array + size
+            }
+            else if (N <= 0xff)
+            {
+                v.push_back(0x98); // one-byte uint8_t for N
+                add_to_vector(v, 1, N);
+            }
+            else if (N <= 0xffff)
+            {
+                v.push_back(0x99); // two-byte uint16_t for N
+                add_to_vector(v, 2, N);
+            }
+            else if (N <= 0xffffffff)
+            {
+                v.push_back(0x9a); // four-byte uint32_t for N
+                add_to_vector(v, 4, N);
+            }
+            // LCOV_EXCL_START
+            else if (N <= 0xffffffffffffffff)
+            {
+                v.push_back(0x9b); // eight-byte uint64_t for N
+                add_to_vector(v, 8, N);
+            }
+            // LCOV_EXCL_STOP
+
+            // append each element
+            for (const auto &el : *j.m_value.array)
+            {
+                to_cbor_internal(el, v);
+            }
+            break;
+        }
+
+        case value_t::object:
+        {
+            const auto N = j.m_value.object->size();
+            if (N <= 0x17)
+            {
+                v.push_back(0xa0 + N); // 1 byte for object + size
+            }
+            else if (N <= 0xff)
+            {
+                v.push_back(0xb8);
+                add_to_vector(v, 1, N); // one-byte uint8_t for N
+            }
+            else if (N <= 0xffff)
+            {
+                v.push_back(0xb9);
+                add_to_vector(v, 2, N); // two-byte uint16_t for N
+            }
+            else if (N <= 0xffffffff)
+            {
+                v.push_back(0xba);
+                add_to_vector(v, 4, N); // four-byte uint32_t for N
+            }
+            // LCOV_EXCL_START
+            else if (N <= 0xffffffffffffffff)
+            {
+                v.push_back(0xbb);
+                add_to_vector(v, 8, N); // eight-byte uint64_t for N
+            }
+            // LCOV_EXCL_STOP
+
+            // append each element
+            for (const auto &el : *j.m_value.object)
+            {
+                to_cbor_internal(el.first, v);
+                to_cbor_internal(el.second, v);
+            }
+            break;
+        }
 
-            // note: at performs range check
-            ptr = &ptr->at(static_cast<size_type>(std::stoi(reference_token)));
-            break;
-          }
-
-          default:
-          {
-            JSON_THROW(std::out_of_range("unresolved reference token '" +
-                                         reference_token + "'"));
-          }
-        }
-      }
-
-      return *ptr;
+        default:
+        {
+            break;
+        }
+        }
     }
 
-    /// split the string input to reference tokens
-    static std::vector<std::string> split(const std::string &reference_string)
+    /*
+    @brief checks if given lengths do not exceed the size of a given vector
+
+    To secure the access to the byte vector during CBOR/MessagePack
+    deserialization, bytes are copied from the vector into buffers. This
+    function checks if the number of bytes to copy (@a len) does not exceed
+    the size @s size of the vector. Additionally, an @a offset is given from
+    where to start reading the bytes.
+
+    This function checks whether reading the bytes is safe; that is, offset is
+    a valid index in the vector, offset+len
+
+    @param[in] size    size of the byte vector
+    @param[in] len     number of bytes to read
+    @param[in] offset  offset where to start reading
+
+    vec:  x x x x x X X X X X
+          ^         ^         ^
+          0         offset    len
+
+    @throws out_of_range if `len > v.size()`
+    */
+    static void check_length(const size_t size, const size_t len,
+                             const size_t offset)
     {
-      std::vector<std::string> result;
+        // 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"));
+        }
 
-      // special case: empty reference string -> no reference tokens
-      if (reference_string.empty())
-      {
-        return result;
-      }
+        // second case: adding offset would result in overflow
+        if ((size > (std::numeric_limits<size_t>::max() - offset)))
+        {
+            JSON_THROW(std::out_of_range("len+offset out of range"));
+        }
 
-      // 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 '/'"));
-      }
-
-      // extract the reference tokens:
-      // - slash: position of the last read slash (or end of string)
-      // - start: position after the previous slash
-      for (
-          // search for the first slash after the first character
-          size_t slash = reference_string.find_first_of('/', 1),
-                 // set the beginning of the first reference token
-          start = 1;
-          // we can stop if start == string::npos+1 = 0
-          start != 0;
-          // set the beginning of the next reference token
-          // (will eventually be 0 if slash == std::string::npos)
-          start = slash + 1,
-                 // find next slash
-          slash = reference_string.find_first_of('/', start))
-      {
-        // use the text between the beginning of the reference token
-        // (start) and the last slash (slash).
-        auto reference_token = reference_string.substr(start, slash - start);
-
-        // check reference tokens are properly escaped
-        for (size_t pos = reference_token.find_first_of('~');
-             pos != std::string::npos;
-             pos = reference_token.find_first_of('~', pos + 1))
-        {
-          assert(reference_token[pos] == '~');
-
-          // ~ must be followed by 0 or 1
-          if (pos == reference_token.size() - 1 or
-              (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'"));
-          }
+        // last case: reading past the end of the vector
+        if (len + offset > size)
+        {
+            JSON_THROW(std::out_of_range("len+offset out of range"));
+        }
+    }
+
+    /*!
+    @brief create a JSON value from a given MessagePack vector
+
+    @param[in] v  MessagePack serialization
+    @param[in] idx  byte index to start reading from @a v
+
+    @return deserialized JSON value
+
+    @throw std::invalid_argument 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
+
+    @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++;
+
+        if (v[current_idx] <= 0xbf)
+        {
+            if (v[current_idx] <= 0x7f) // positive fixint
+            {
+                return v[current_idx];
+            }
+            if (v[current_idx] <= 0x8f) // fixmap
+            {
+                basic_json result = value_t::object;
+                const size_t len = v[current_idx] & 0x0f;
+                for (size_t i = 0; i < len; ++i)
+                {
+                    std::string key = from_msgpack_internal(v, idx);
+                    result[key] = from_msgpack_internal(v, idx);
+                }
+                return result;
+            }
+            else if (v[current_idx] <= 0x9f) // fixarray
+            {
+                basic_json result = value_t::array;
+                const size_t len = v[current_idx] & 0x0f;
+                for (size_t i = 0; i < len; ++i)
+                {
+                    result.push_back(from_msgpack_internal(v, idx));
+                }
+                return result;
+            }
+            else // fixstr
+            {
+                const size_t len = v[current_idx] & 0x1f;
+                const size_t offset = current_idx + 1;
+                idx += len; // skip content bytes
+                check_length(v.size(), len, offset);
+                return std::string(
+                    reinterpret_cast<const char *>(v.data()) + offset, len);
+            }
+        }
+        else if (v[current_idx] >= 0xe0) // negative fixint
+        {
+            return static_cast<int8_t>(v[current_idx]);
+        }
+        else
+        {
+            switch (v[current_idx])
+            {
+            case 0xc0: // nil
+            {
+                return value_t::null;
+            }
+
+            case 0xc2: // false
+            {
+                return false;
+            }
+
+            case 0xc3: // true
+            {
+                return true;
+            }
+
+            case 0xca: // float 32
+            {
+                // copy bytes in reverse order into the double variable
+                float res;
+                for (size_t byte = 0; byte < sizeof(float); ++byte)
+                {
+                    reinterpret_cast<uint8_t *>(
+                        &res)[sizeof(float) - byte - 1] =
+                        v.at(current_idx + 1 + byte);
+                }
+                idx += sizeof(float); // skip content bytes
+                return res;
+            }
+
+            case 0xcb: // float 64
+            {
+                // copy bytes in reverse order into the double variable
+                double res;
+                for (size_t byte = 0; byte < sizeof(double); ++byte)
+                {
+                    reinterpret_cast<uint8_t *>(
+                        &res)[sizeof(double) - byte - 1] =
+                        v.at(current_idx + 1 + byte);
+                }
+                idx += sizeof(double); // skip content bytes
+                return res;
+            }
+
+            case 0xcc: // uint 8
+            {
+                idx += 1; // skip content byte
+                return get_from_vector<uint8_t>(v, current_idx);
+            }
+
+            case 0xcd: // uint 16
+            {
+                idx += 2; // skip 2 content bytes
+                return get_from_vector<uint16_t>(v, current_idx);
+            }
+
+            case 0xce: // uint 32
+            {
+                idx += 4; // skip 4 content bytes
+                return get_from_vector<uint32_t>(v, current_idx);
+            }
+
+            case 0xcf: // uint 64
+            {
+                idx += 8; // skip 8 content bytes
+                return get_from_vector<uint64_t>(v, current_idx);
+            }
+
+            case 0xd0: // int 8
+            {
+                idx += 1; // skip content byte
+                return get_from_vector<int8_t>(v, current_idx);
+            }
+
+            case 0xd1: // int 16
+            {
+                idx += 2; // skip 2 content bytes
+                return get_from_vector<int16_t>(v, current_idx);
+            }
+
+            case 0xd2: // int 32
+            {
+                idx += 4; // skip 4 content bytes
+                return get_from_vector<int32_t>(v, current_idx);
+            }
+
+            case 0xd3: // int 64
+            {
+                idx += 8; // skip 8 content bytes
+                return get_from_vector<int64_t>(v, current_idx);
+            }
+
+            case 0xd9: // str 8
+            {
+                const auto len = static_cast<size_t>(
+                    get_from_vector<uint8_t>(v, current_idx));
+                const size_t offset = current_idx + 2;
+                idx += len + 1; // skip size byte + content bytes
+                check_length(v.size(), len, offset);
+                return std::string(
+                    reinterpret_cast<const char *>(v.data()) + offset, len);
+            }
+
+            case 0xda: // str 16
+            {
+                const auto len = static_cast<size_t>(
+                    get_from_vector<uint16_t>(v, current_idx));
+                const size_t offset = current_idx + 3;
+                idx += len + 2; // skip 2 size bytes + content bytes
+                check_length(v.size(), len, offset);
+                return std::string(
+                    reinterpret_cast<const char *>(v.data()) + offset, len);
+            }
+
+            case 0xdb: // str 32
+            {
+                const auto len = static_cast<size_t>(
+                    get_from_vector<uint32_t>(v, current_idx));
+                const size_t offset = current_idx + 5;
+                idx += len + 4; // skip 4 size bytes + content bytes
+                check_length(v.size(), len, offset);
+                return std::string(
+                    reinterpret_cast<const char *>(v.data()) + offset, len);
+            }
+
+            case 0xdc: // array 16
+            {
+                basic_json result = value_t::array;
+                const auto len = static_cast<size_t>(
+                    get_from_vector<uint16_t>(v, current_idx));
+                idx += 2; // skip 2 size bytes
+                for (size_t i = 0; i < len; ++i)
+                {
+                    result.push_back(from_msgpack_internal(v, idx));
+                }
+                return result;
+            }
+
+            case 0xdd: // array 32
+            {
+                basic_json result = value_t::array;
+                const auto len = static_cast<size_t>(
+                    get_from_vector<uint32_t>(v, current_idx));
+                idx += 4; // skip 4 size bytes
+                for (size_t i = 0; i < len; ++i)
+                {
+                    result.push_back(from_msgpack_internal(v, idx));
+                }
+                return result;
+            }
+
+            case 0xde: // map 16
+            {
+                basic_json result = value_t::object;
+                const auto len = static_cast<size_t>(
+                    get_from_vector<uint16_t>(v, current_idx));
+                idx += 2; // skip 2 size bytes
+                for (size_t i = 0; i < len; ++i)
+                {
+                    std::string key = from_msgpack_internal(v, idx);
+                    result[key] = from_msgpack_internal(v, idx);
+                }
+                return result;
+            }
+
+            case 0xdf: // map 32
+            {
+                basic_json result = value_t::object;
+                const auto len = static_cast<size_t>(
+                    get_from_vector<uint32_t>(v, current_idx));
+                idx += 4; // skip 4 size bytes
+                for (size_t i = 0; i < len; ++i)
+                {
+                    std::string key = from_msgpack_internal(v, idx);
+                    result[key] = from_msgpack_internal(v, idx);
+                }
+                return result;
+            }
+
+            default:
+            {
+                JSON_THROW(std::invalid_argument(
+                    "error parsing a msgpack @ " + std::to_string(current_idx) +
+                    ": " + std::to_string(static_cast<int>(v[current_idx]))));
+            }
+            }
+        }
+    }
+
+    /*!
+    @brief create a JSON value from a given CBOR vector
+
+    @param[in] v  CBOR serialization
+    @param[in] idx  byte index to start reading from @a v
+
+    @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
+
+    @sa https://tools.ietf.org/html/rfc7049
+    */
+    static basic_json from_cbor_internal(const std::vector<uint8_t> &v,
+                                         size_t &idx)
+    {
+        // store and increment index
+        const size_t current_idx = idx++;
+
+        switch (v.at(current_idx))
+        {
+        // Integer 0x00..0x17 (0..23)
+        case 0x00:
+        case 0x01:
+        case 0x02:
+        case 0x03:
+        case 0x04:
+        case 0x05:
+        case 0x06:
+        case 0x07:
+        case 0x08:
+        case 0x09:
+        case 0x0a:
+        case 0x0b:
+        case 0x0c:
+        case 0x0d:
+        case 0x0e:
+        case 0x0f:
+        case 0x10:
+        case 0x11:
+        case 0x12:
+        case 0x13:
+        case 0x14:
+        case 0x15:
+        case 0x16:
+        case 0x17:
+        {
+            return v[current_idx];
+        }
+
+        case 0x18: // Unsigned integer (one-byte uint8_t follows)
+        {
+            idx += 1; // skip content byte
+            return get_from_vector<uint8_t>(v, current_idx);
+        }
+
+        case 0x19: // Unsigned integer (two-byte uint16_t follows)
+        {
+            idx += 2; // skip 2 content bytes
+            return get_from_vector<uint16_t>(v, current_idx);
+        }
+
+        case 0x1a: // Unsigned integer (four-byte uint32_t follows)
+        {
+            idx += 4; // skip 4 content bytes
+            return get_from_vector<uint32_t>(v, current_idx);
+        }
+
+        case 0x1b: // Unsigned integer (eight-byte uint64_t follows)
+        {
+            idx += 8; // skip 8 content bytes
+            return get_from_vector<uint64_t>(v, current_idx);
+        }
+
+        // Negative integer -1-0x00..-1-0x17 (-1..-24)
+        case 0x20:
+        case 0x21:
+        case 0x22:
+        case 0x23:
+        case 0x24:
+        case 0x25:
+        case 0x26:
+        case 0x27:
+        case 0x28:
+        case 0x29:
+        case 0x2a:
+        case 0x2b:
+        case 0x2c:
+        case 0x2d:
+        case 0x2e:
+        case 0x2f:
+        case 0x30:
+        case 0x31:
+        case 0x32:
+        case 0x33:
+        case 0x34:
+        case 0x35:
+        case 0x36:
+        case 0x37:
+        {
+            return static_cast<int8_t>(0x20 - 1 - v[current_idx]);
+        }
+
+        case 0x38: // Negative integer (one-byte uint8_t follows)
+        {
+            idx += 1; // skip content byte
+            // must be uint8_t !
+            return static_cast<number_integer_t>(-1) -
+                   get_from_vector<uint8_t>(v, current_idx);
+        }
+
+        case 0x39: // Negative integer -1-n (two-byte uint16_t follows)
+        {
+            idx += 2; // skip 2 content bytes
+            return static_cast<number_integer_t>(-1) -
+                   get_from_vector<uint16_t>(v, current_idx);
+        }
+
+        case 0x3a: // Negative integer -1-n (four-byte uint32_t follows)
+        {
+            idx += 4; // skip 4 content bytes
+            return static_cast<number_integer_t>(-1) -
+                   get_from_vector<uint32_t>(v, current_idx);
+        }
+
+        case 0x3b: // Negative integer -1-n (eight-byte uint64_t follows)
+        {
+            idx += 8; // skip 8 content bytes
+            return static_cast<number_integer_t>(-1) -
+                   static_cast<number_integer_t>(
+                       get_from_vector<uint64_t>(v, current_idx));
+        }
+
+        // UTF-8 string (0x00..0x17 bytes follow)
+        case 0x60:
+        case 0x61:
+        case 0x62:
+        case 0x63:
+        case 0x64:
+        case 0x65:
+        case 0x66:
+        case 0x67:
+        case 0x68:
+        case 0x69:
+        case 0x6a:
+        case 0x6b:
+        case 0x6c:
+        case 0x6d:
+        case 0x6e:
+        case 0x6f:
+        case 0x70:
+        case 0x71:
+        case 0x72:
+        case 0x73:
+        case 0x74:
+        case 0x75:
+        case 0x76:
+        case 0x77:
+        {
+            const auto len = static_cast<size_t>(v[current_idx] - 0x60);
+            const size_t offset = current_idx + 1;
+            idx += len; // skip content bytes
+            check_length(v.size(), len, offset);
+            return std::string(
+                reinterpret_cast<const char *>(v.data()) + offset, len);
+        }
+
+        case 0x78: // UTF-8 string (one-byte uint8_t for n follows)
+        {
+            const auto len =
+                static_cast<size_t>(get_from_vector<uint8_t>(v, current_idx));
+            const size_t offset = current_idx + 2;
+            idx += len + 1; // skip size byte + content bytes
+            check_length(v.size(), len, offset);
+            return std::string(
+                reinterpret_cast<const char *>(v.data()) + offset, len);
+        }
+
+        case 0x79: // UTF-8 string (two-byte uint16_t for n follow)
+        {
+            const auto len =
+                static_cast<size_t>(get_from_vector<uint16_t>(v, current_idx));
+            const size_t offset = current_idx + 3;
+            idx += len + 2; // skip 2 size bytes + content bytes
+            check_length(v.size(), len, offset);
+            return std::string(
+                reinterpret_cast<const char *>(v.data()) + offset, len);
+        }
+
+        case 0x7a: // UTF-8 string (four-byte uint32_t for n follow)
+        {
+            const auto len =
+                static_cast<size_t>(get_from_vector<uint32_t>(v, current_idx));
+            const size_t offset = current_idx + 5;
+            idx += len + 4; // skip 4 size bytes + content bytes
+            check_length(v.size(), len, offset);
+            return std::string(
+                reinterpret_cast<const char *>(v.data()) + offset, len);
+        }
+
+        case 0x7b: // UTF-8 string (eight-byte uint64_t for n follow)
+        {
+            const auto len =
+                static_cast<size_t>(get_from_vector<uint64_t>(v, current_idx));
+            const size_t offset = current_idx + 9;
+            idx += len + 8; // skip 8 size bytes + content bytes
+            check_length(v.size(), len, offset);
+            return std::string(
+                reinterpret_cast<const char *>(v.data()) + offset, len);
+        }
+
+        case 0x7f: // UTF-8 string (indefinite length)
+        {
+            std::string result;
+            while (v.at(idx) != 0xff)
+            {
+                string_t s = from_cbor_internal(v, idx);
+                result += s;
+            }
+            // skip break byte (0xFF)
+            idx += 1;
+            return result;
+        }
+
+        // array (0x00..0x17 data items follow)
+        case 0x80:
+        case 0x81:
+        case 0x82:
+        case 0x83:
+        case 0x84:
+        case 0x85:
+        case 0x86:
+        case 0x87:
+        case 0x88:
+        case 0x89:
+        case 0x8a:
+        case 0x8b:
+        case 0x8c:
+        case 0x8d:
+        case 0x8e:
+        case 0x8f:
+        case 0x90:
+        case 0x91:
+        case 0x92:
+        case 0x93:
+        case 0x94:
+        case 0x95:
+        case 0x96:
+        case 0x97:
+        {
+            basic_json result = value_t::array;
+            const auto len = static_cast<size_t>(v[current_idx] - 0x80);
+            for (size_t i = 0; i < len; ++i)
+            {
+                result.push_back(from_cbor_internal(v, idx));
+            }
+            return result;
+        }
+
+        case 0x98: // array (one-byte uint8_t for n follows)
+        {
+            basic_json result = value_t::array;
+            const auto len =
+                static_cast<size_t>(get_from_vector<uint8_t>(v, current_idx));
+            idx += 1; // skip 1 size byte
+            for (size_t i = 0; i < len; ++i)
+            {
+                result.push_back(from_cbor_internal(v, idx));
+            }
+            return result;
+        }
+
+        case 0x99: // array (two-byte uint16_t for n follow)
+        {
+            basic_json result = value_t::array;
+            const auto len =
+                static_cast<size_t>(get_from_vector<uint16_t>(v, current_idx));
+            idx += 2; // skip 4 size bytes
+            for (size_t i = 0; i < len; ++i)
+            {
+                result.push_back(from_cbor_internal(v, idx));
+            }
+            return result;
+        }
+
+        case 0x9a: // array (four-byte uint32_t for n follow)
+        {
+            basic_json result = value_t::array;
+            const auto len =
+                static_cast<size_t>(get_from_vector<uint32_t>(v, current_idx));
+            idx += 4; // skip 4 size bytes
+            for (size_t i = 0; i < len; ++i)
+            {
+                result.push_back(from_cbor_internal(v, idx));
+            }
+            return result;
+        }
+
+        case 0x9b: // array (eight-byte uint64_t for n follow)
+        {
+            basic_json result = value_t::array;
+            const auto len =
+                static_cast<size_t>(get_from_vector<uint64_t>(v, current_idx));
+            idx += 8; // skip 8 size bytes
+            for (size_t i = 0; i < len; ++i)
+            {
+                result.push_back(from_cbor_internal(v, idx));
+            }
+            return result;
+        }
+
+        case 0x9f: // array (indefinite length)
+        {
+            basic_json result = value_t::array;
+            while (v.at(idx) != 0xff)
+            {
+                result.push_back(from_cbor_internal(v, idx));
+            }
+            // skip break byte (0xFF)
+            idx += 1;
+            return result;
         }
 
-        // finally, store the reference token
-        unescape(reference_token);
-        result.push_back(reference_token);
-      }
+        // map (0x00..0x17 pairs of data items follow)
+        case 0xa0:
+        case 0xa1:
+        case 0xa2:
+        case 0xa3:
+        case 0xa4:
+        case 0xa5:
+        case 0xa6:
+        case 0xa7:
+        case 0xa8:
+        case 0xa9:
+        case 0xaa:
+        case 0xab:
+        case 0xac:
+        case 0xad:
+        case 0xae:
+        case 0xaf:
+        case 0xb0:
+        case 0xb1:
+        case 0xb2:
+        case 0xb3:
+        case 0xb4:
+        case 0xb5:
+        case 0xb6:
+        case 0xb7:
+        {
+            basic_json result = value_t::object;
+            const auto len = static_cast<size_t>(v[current_idx] - 0xa0);
+            for (size_t i = 0; i < len; ++i)
+            {
+                std::string key = from_cbor_internal(v, idx);
+                result[key] = from_cbor_internal(v, idx);
+            }
+            return result;
+        }
+
+        case 0xb8: // map (one-byte uint8_t for n follows)
+        {
+            basic_json result = value_t::object;
+            const auto len =
+                static_cast<size_t>(get_from_vector<uint8_t>(v, current_idx));
+            idx += 1; // skip 1 size byte
+            for (size_t i = 0; i < len; ++i)
+            {
+                std::string key = from_cbor_internal(v, idx);
+                result[key] = from_cbor_internal(v, idx);
+            }
+            return result;
+        }
+
+        case 0xb9: // map (two-byte uint16_t for n follow)
+        {
+            basic_json result = value_t::object;
+            const auto len =
+                static_cast<size_t>(get_from_vector<uint16_t>(v, current_idx));
+            idx += 2; // skip 2 size bytes
+            for (size_t i = 0; i < len; ++i)
+            {
+                std::string key = from_cbor_internal(v, idx);
+                result[key] = from_cbor_internal(v, idx);
+            }
+            return result;
+        }
+
+        case 0xba: // map (four-byte uint32_t for n follow)
+        {
+            basic_json result = value_t::object;
+            const auto len =
+                static_cast<size_t>(get_from_vector<uint32_t>(v, current_idx));
+            idx += 4; // skip 4 size bytes
+            for (size_t i = 0; i < len; ++i)
+            {
+                std::string key = from_cbor_internal(v, idx);
+                result[key] = from_cbor_internal(v, idx);
+            }
+            return result;
+        }
+
+        case 0xbb: // map (eight-byte uint64_t for n follow)
+        {
+            basic_json result = value_t::object;
+            const auto len =
+                static_cast<size_t>(get_from_vector<uint64_t>(v, current_idx));
+            idx += 8; // skip 8 size bytes
+            for (size_t i = 0; i < len; ++i)
+            {
+                std::string key = from_cbor_internal(v, idx);
+                result[key] = from_cbor_internal(v, idx);
+            }
+            return result;
+        }
+
+        case 0xbf: // map (indefinite length)
+        {
+            basic_json result = value_t::object;
+            while (v.at(idx) != 0xff)
+            {
+                std::string key = from_cbor_internal(v, idx);
+                result[key] = from_cbor_internal(v, idx);
+            }
+            // skip break byte (0xFF)
+            idx += 1;
+            return result;
+        }
+
+        case 0xf4: // false
+        {
+            return false;
+        }
+
+        case 0xf5: // true
+        {
+            return true;
+        }
+
+        case 0xf6: // null
+        {
+            return value_t::null;
+        }
+
+        case 0xf9: // Half-Precision Float (two-byte IEEE 754)
+        {
+            idx += 2; // skip two content bytes
+
+            // code from RFC 7049, Appendix D, Figure 3:
+            // As half-precision floating-point numbers were only added to
+            // IEEE 754 in 2008, today's programming platforms often still
+            // only have limited support for them. It is very easy to
+            // 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);
+            const int exp = (half >> 10) & 0x1f;
+            const int mant = half & 0x3ff;
+            double val;
+            if (exp == 0)
+            {
+                val = std::ldexp(mant, -24);
+            }
+            else if (exp != 31)
+            {
+                val = std::ldexp(mant + 1024, exp - 25);
+            }
+            else
+            {
+                val = mant == 0 ? INFINITY : NAN;
+            }
+            return (half & 0x8000) != 0 ? -val : val;
+        }
+
+        case 0xfa: // Single-Precision Float (four-byte IEEE 754)
+        {
+            // copy bytes in reverse order into the float variable
+            float res;
+            for (size_t byte = 0; byte < sizeof(float); ++byte)
+            {
+                reinterpret_cast<uint8_t *>(&res)[sizeof(float) - byte - 1] =
+                    v.at(current_idx + 1 + byte);
+            }
+            idx += sizeof(float); // skip content bytes
+            return res;
+        }
+
+        case 0xfb: // Double-Precision Float (eight-byte IEEE 754)
+        {
+            // copy bytes in reverse order into the double variable
+            double res;
+            for (size_t byte = 0; byte < sizeof(double); ++byte)
+            {
+                reinterpret_cast<uint8_t *>(&res)[sizeof(double) - byte - 1] =
+                    v.at(current_idx + 1 + byte);
+            }
+            idx += sizeof(double); // skip content bytes
+            return res;
+        }
+
+        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]))));
+        }
+        }
+    }
+
+public:
+    /*!
+    @brief create a MessagePack serialization of a given JSON value
+
+    Serializes a given JSON value @a j to a byte vector using the MessagePack
+    serialization format. MessagePack is a binary serialization format which
+    aims to be more compact than JSON itself, yet more efficient to parse.
+
+    @param[in] j  JSON value to serialize
+    @return MessagePack serialization as byte vector
+
+    @complexity Linear in the size of the JSON value @a j.
+
+    @liveexample{The example shows the serialization of a JSON value to a byte
+    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 to_cbor(const basic_json& for the related CBOR format
+    */
+    static std::vector<uint8_t> to_msgpack(const basic_json &j)
+    {
+        std::vector<uint8_t> result;
+        to_msgpack_internal(j, result);
+        return result;
+    }
+
+    /*!
+    @brief create a JSON value from a byte vector in MessagePack format
+
+    Deserializes a given byte vector @a v to a JSON value using the MessagePack
+    serialization format.
+
+    @param[in] v  a byte vector in MessagePack format
+    @return deserialized JSON value
+
+    @throw std::invalid_argument 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
+
+    @complexity Linear in the size of the byte vector @a v.
+
+    @liveexample{The example shows the deserialization of a byte vector in
+    MessagePack format to a JSON value.,from_msgpack}
+
+    @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
+    */
+    static basic_json from_msgpack(const std::vector<uint8_t> &v)
+    {
+        size_t i = 0;
+        return from_msgpack_internal(v, i);
+    }
+
+    /*!
+    @brief create a MessagePack serialization of a given JSON value
+
+    Serializes a given JSON value @a j to a byte vector using the CBOR (Concise
+    Binary Object Representation) serialization format. CBOR is a binary
+    serialization format which aims to be more compact than JSON itself, yet
+    more efficient to parse.
+
+    @param[in] j  JSON value to serialize
+    @return MessagePack serialization as byte vector
+
+    @complexity Linear in the size of the JSON value @a j.
+
+    @liveexample{The example shows the serialization of a JSON value to a byte
+    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 to_msgpack(const basic_json& for the related MessagePack format
+    */
+    static std::vector<uint8_t> to_cbor(const basic_json &j)
+    {
+        std::vector<uint8_t> result;
+        to_cbor_internal(j, result);
+        return result;
+    }
+
+    /*!
+    @brief create a JSON value from a byte vector in CBOR format
+
+    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";
+            }
+
+            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);
+            }
+
+            // decrease indentation
+            if (pretty_print)
+            {
+                new_indent -= indent_step;
+                o << "\n";
+            }
+
+            o << string_t(new_indent, ' ') + "}";
+            return;
+        }
+
+        case value_t::array:
+        {
+            if (m_value.array->empty())
+            {
+                o << "[]";
+                return;
+            }
+
+            o << "[";
+
+            // increase indentation
+            if (pretty_print)
+            {
+                new_indent += indent_step;
+                o << "\n";
+            }
+
+            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";
+            }
+
+            o << string_t(new_indent, ' ') << "]";
+            return;
+        }
+
+        case value_t::string:
+        {
+            o << string_t("\"") << escape_string(*m_value.string) << "\"";
+            return;
+        }
+
+        case value_t::boolean:
+        {
+            o << (m_value.boolean ? "true" : "false");
+            return;
+        }
+
+        case value_t::number_integer:
+        {
+            o << m_value.number_integer;
+            return;
+        }
+
+        case value_t::number_unsigned:
+        {
+            o << m_value.number_unsigned;
+            return;
+        }
+
+        case value_t::number_float:
+        {
+            if (m_value.number_float == 0)
+            {
+                // 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;
+            }
+            return;
+        }
+
+        case value_t::discarded:
+        {
+            o << "<discarded>";
+            return;
+        }
+
+        case value_t::null:
+        {
+            o << "null";
+            return;
+        }
+        }
+    }
+
+private:
+    //////////////////////
+    // member variables //
+    //////////////////////
+
+    /// the type of the current element
+    value_t m_type = value_t::null;
+
+    /// the value of the current element
+    json_value m_value = {};
+
+private:
+    ///////////////
+    // iterators //
+    ///////////////
+
+    /*!
+    @brief an iterator for primitive JSON types
+
+    This class models an iterator for primitive JSON types (boolean, number,
+    string). It's only purpose is to allow the iterator/const_iterator classes
+    to "iterate" over primitive values. Internally, the iterator is modeled by
+    a `difference_type` variable. Value begin_value (`0`) models the begin,
+    end_value (`1`) models past the end.
+    */
+    class primitive_iterator_t
+    {
+    public:
+        /// set iterator to a defined beginning
+        void set_begin() noexcept { m_it = begin_value; }
+
+        /// set iterator to a defined past the end
+        void set_end() noexcept { m_it = end_value; }
+
+        /// return whether the iterator can be dereferenced
+        constexpr bool is_begin() const noexcept
+        {
+            return (m_it == begin_value);
+        }
+
+        /// 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; }
+
+        /// return value to compare
+        constexpr operator difference_type() const noexcept { return m_it; }
+
+    private:
+        static constexpr difference_type begin_value = 0;
+        static constexpr difference_type end_value = begin_value + 1;
+
+        /// iterator as signed integer type
+        difference_type m_it =
+            std::numeric_limits<std::ptrdiff_t>::denorm_min();
+    };
+
+    /*!
+    @brief an iterator value
+
+    @note This structure could easily be a union, but MSVC currently does not
+    allow unions members with complex constructors, see
+    https://github.com/nlohmann/json/pull/105.
+    */
+    struct internal_iterator
+    {
+        /// iterator for JSON objects
+        typename object_t::iterator object_iterator;
+        /// iterator for JSON arrays
+        typename array_t::iterator array_iterator;
+        /// generic iterator for all other types
+        primitive_iterator_t primitive_iterator;
+
+        /// create an uninitialized internal_iterator
+        internal_iterator() noexcept : object_iterator(),
+                                       array_iterator(),
+                                       primitive_iterator()
+        {
+        }
+    };
+
+    /// proxy class for the iterator_wrapper functions
+    template <typename IteratorType>
+    class iteration_proxy
+    {
+    private:
+        /// helper class for iteration
+        class iteration_proxy_internal
+        {
+        private:
+            /// the iterator
+            IteratorType anchor;
+            /// an index for arrays (used to create key names)
+            size_t array_index = 0;
+
+        public:
+            explicit iteration_proxy_internal(IteratorType it) noexcept
+                : anchor(it)
+            {
+            }
+
+            /// dereference operator (needed for range-based for)
+            iteration_proxy_internal &operator*() { return *this; }
+
+            /// increment operator (needed for range-based for)
+            iteration_proxy_internal &operator++()
+            {
+                ++anchor;
+                ++array_index;
+
+                return *this;
+            }
+
+            /// inequality operator (needed for range-based for)
+            bool operator!=(const iteration_proxy_internal &o) const
+            {
+                return anchor != o.anchor;
+            }
+
+            /// return key of the iterator
+            typename basic_json::string_t key() const
+            {
+                assert(anchor.m_object != nullptr);
+
+                switch (anchor.m_object->type())
+                {
+                // use integer array index as key
+                case value_t::array:
+                {
+                    return std::to_string(array_index);
+                }
+
+                // use key from the object
+                case value_t::object:
+                {
+                    return anchor.key();
+                }
+
+                // use an empty key for all primitive types
+                default:
+                {
+                    return "";
+                }
+                }
+            }
+
+            /// return value of the iterator
+            typename IteratorType::reference value() const
+            {
+                return anchor.value();
+            }
+        };
+
+        /// the container to iterate
+        typename IteratorType::reference container;
+
+    public:
+        /// construct iteration proxy from a container
+        explicit iteration_proxy(typename IteratorType::reference cont)
+        : container(cont)
+        {
+        }
+
+        /// return iterator begin (needed for range-based for)
+        iteration_proxy_internal begin() noexcept
+        {
+            return iteration_proxy_internal(container.begin());
+        }
+
+        /// return iterator end (needed for range-based for)
+        iteration_proxy_internal end() noexcept
+        {
+            return iteration_proxy_internal(container.end());
+        }
+    };
+
+public:
+    /*!
+    @brief a template for a random access iterator for the @ref basic_json class
+
+    This class implements a both iterators (iterator and const_iterator) for the
+    @ref basic_json class.
+
+    @note An iterator is called *initialized* when a pointer to a JSON value
+          has been set (e.g., by a constructor or a copy assignment). If the
+          iterator is default-constructed, it is *uninitialized* and most
+          methods are undefined. **The library uses assertions to detect calls
+          on uninitialized iterators.**
+
+    @requirement The class satisfies the following concept requirements:
+    -
+    [RandomAccessIterator](http://en.cppreference.com/w/cpp/concept/RandomAccessIterator):
+      The iterator that can be moved to point (forward and backward) to any
+      element in constant time.
+
+    @since version 1.0.0, simplified in version 2.0.9
+    */
+    template <typename U>
+    class iter_impl : public std::iterator<std::random_access_iterator_tag, U>
+    {
+        /// allow basic_json to access private members
+        friend class basic_json;
+
+        // make sure U is basic_json or const basic_json
+        static_assert(std::is_same<U, basic_json>::value or
+                          std::is_same<U, const basic_json>::value,
+                      "iter_impl only accepts (const) basic_json");
+
+    public:
+        /// the type of the values when the iterator is dereferenced
+        using value_type = typename basic_json::value_type;
+        /// a type to represent differences between iterators
+        using difference_type = typename basic_json::difference_type;
+        /// defines a pointer to the type iterated over (value_type)
+        using pointer =
+            typename std::conditional<std::is_const<U>::value,
+                                      typename basic_json::const_pointer,
+                                      typename basic_json::pointer>::type;
+        /// defines a reference to the type iterated over (value_type)
+        using reference =
+            typename std::conditional<std::is_const<U>::value,
+                                      typename basic_json::const_reference,
+                                      typename basic_json::reference>::type;
+        /// the category of the iterator
+        using iterator_category = std::bidirectional_iterator_tag;
+
+        /// default constructor
+        iter_impl() = default;
+
+        /*!
+        @brief constructor for a given JSON instance
+        @param[in] object  pointer to a JSON object for this iterator
+        @pre object != nullptr
+        @post The iterator is initialized; i.e. `m_object != nullptr`.
+        */
+        explicit iter_impl(pointer object) noexcept : m_object(object)
+        {
+            assert(m_object != nullptr);
+
+            switch (m_object->m_type)
+            {
+            case basic_json::value_t::object:
+            {
+                m_it.object_iterator = typename object_t::iterator();
+                break;
+            }
+
+            case basic_json::value_t::array:
+            {
+                m_it.array_iterator = typename array_t::iterator();
+                break;
+            }
+
+            default:
+            {
+                m_it.primitive_iterator = primitive_iterator_t();
+                break;
+            }
+            }
+        }
+
+        /*
+        Use operator `const_iterator` instead of `const_iterator(const iterator&
+        other) noexcept` to avoid two class definitions for @ref iterator and
+        @ref const_iterator.
+
+        This function is only called if this class is an @ref iterator. If this
+        class is a @ref const_iterator this function is not called.
+        */
+        operator const_iterator() const
+        {
+            const_iterator ret;
+
+            if (m_object)
+            {
+                ret.m_object = m_object;
+                ret.m_it = m_it;
+            }
+
+            return ret;
+        }
+
+        /*!
+        @brief copy constructor
+        @param[in] other  iterator to copy from
+        @note It is not checked whether @a other is initialized.
+        */
+        iter_impl(const iter_impl &other) noexcept : m_object(other.m_object),
+                                                     m_it(other.m_it)
+        {
+        }
+
+        /*!
+        @brief copy assignment
+        @param[in,out] other  iterator to copy from
+        @note It is not checked whether @a other is initialized.
+        */
+        iter_impl &operator=(iter_impl other) noexcept(
+            std::is_nothrow_move_constructible<pointer>::value
+                and std::is_nothrow_move_assignable<pointer>::value and
+                    std::is_nothrow_move_constructible<internal_iterator>::value
+                        and std::is_nothrow_move_assignable<
+                            internal_iterator>::value)
+        {
+            std::swap(m_object, other.m_object);
+            std::swap(m_it, other.m_it);
+            return *this;
+        }
+
+    private:
+        /*!
+        @brief set the iterator to the first value
+        @pre The iterator is initialized; i.e. `m_object != nullptr`.
+        */
+        void set_begin() noexcept
+        {
+            assert(m_object != nullptr);
+
+            switch (m_object->m_type)
+            {
+            case basic_json::value_t::object:
+            {
+                m_it.object_iterator = m_object->m_value.object->begin();
+                break;
+            }
+
+            case basic_json::value_t::array:
+            {
+                m_it.array_iterator = m_object->m_value.array->begin();
+                break;
+            }
+
+            case basic_json::value_t::null:
+            {
+                // set to end so begin()==end() is true: null is empty
+                m_it.primitive_iterator.set_end();
+                break;
+            }
+
+            default:
+            {
+                m_it.primitive_iterator.set_begin();
+                break;
+            }
+            }
+        }
+
+        /*!
+        @brief set the iterator past the last value
+        @pre The iterator is initialized; i.e. `m_object != nullptr`.
+        */
+        void set_end() noexcept
+        {
+            assert(m_object != nullptr);
+
+            switch (m_object->m_type)
+            {
+            case basic_json::value_t::object:
+            {
+                m_it.object_iterator = m_object->m_value.object->end();
+                break;
+            }
+
+            case basic_json::value_t::array:
+            {
+                m_it.array_iterator = m_object->m_value.array->end();
+                break;
+            }
+
+            default:
+            {
+                m_it.primitive_iterator.set_end();
+                break;
+            }
+            }
+        }
+
+    public:
+        /*!
+        @brief return a reference to the value pointed to by the iterator
+        @pre The iterator is initialized; i.e. `m_object != nullptr`.
+        */
+        reference operator*() const
+        {
+            assert(m_object != nullptr);
+
+            switch (m_object->m_type)
+            {
+            case basic_json::value_t::object:
+            {
+                assert(m_it.object_iterator != m_object->m_value.object->end());
+                return m_it.object_iterator->second;
+            }
+
+            case basic_json::value_t::array:
+            {
+                assert(m_it.array_iterator != m_object->m_value.array->end());
+                return *m_it.array_iterator;
+            }
+
+            case basic_json::value_t::null:
+            {
+                JSON_THROW(std::out_of_range("cannot get value"));
+            }
+
+            default:
+            {
+                if (m_it.primitive_iterator.is_begin())
+                {
+                    return *m_object;
+                }
+
+                JSON_THROW(std::out_of_range("cannot get value"));
+            }
+            }
+        }
+
+        /*!
+        @brief dereference the iterator
+        @pre The iterator is initialized; i.e. `m_object != nullptr`.
+        */
+        pointer operator->() const
+        {
+            assert(m_object != nullptr);
+
+            switch (m_object->m_type)
+            {
+            case basic_json::value_t::object:
+            {
+                assert(m_it.object_iterator != m_object->m_value.object->end());
+                return &(m_it.object_iterator->second);
+            }
+
+            case basic_json::value_t::array:
+            {
+                assert(m_it.array_iterator != m_object->m_value.array->end());
+                return &*m_it.array_iterator;
+            }
+
+            default:
+            {
+                if (m_it.primitive_iterator.is_begin())
+                {
+                    return m_object;
+                }
+
+                JSON_THROW(std::out_of_range("cannot get value"));
+            }
+            }
+        }
+
+        /*!
+        @brief post-increment (it++)
+        @pre The iterator is initialized; i.e. `m_object != nullptr`.
+        */
+        iter_impl operator++(int)
+        {
+            auto result = *this;
+            ++(*this);
+            return result;
+        }
+
+        /*!
+        @brief pre-increment (++it)
+        @pre The iterator is initialized; i.e. `m_object != nullptr`.
+        */
+        iter_impl &operator++()
+        {
+            assert(m_object != nullptr);
+
+            switch (m_object->m_type)
+            {
+            case basic_json::value_t::object:
+            {
+                std::advance(m_it.object_iterator, 1);
+                break;
+            }
+
+            case basic_json::value_t::array:
+            {
+                std::advance(m_it.array_iterator, 1);
+                break;
+            }
+
+            default:
+            {
+                ++m_it.primitive_iterator;
+                break;
+            }
+            }
+
+            return *this;
+        }
+
+        /*!
+        @brief post-decrement (it--)
+        @pre The iterator is initialized; i.e. `m_object != nullptr`.
+        */
+        iter_impl operator--(int)
+        {
+            auto result = *this;
+            --(*this);
+            return result;
+        }
+
+        /*!
+        @brief pre-decrement (--it)
+        @pre The iterator is initialized; i.e. `m_object != nullptr`.
+        */
+        iter_impl &operator--()
+        {
+            assert(m_object != nullptr);
+
+            switch (m_object->m_type)
+            {
+            case basic_json::value_t::object:
+            {
+                std::advance(m_it.object_iterator, -1);
+                break;
+            }
+
+            case basic_json::value_t::array:
+            {
+                std::advance(m_it.array_iterator, -1);
+                break;
+            }
+
+            default:
+            {
+                --m_it.primitive_iterator;
+                break;
+            }
+            }
+
+            return *this;
+        }
+
+        /*!
+        @brief  comparison: equal
+        @pre The iterator is initialized; i.e. `m_object != nullptr`.
+        */
+        bool operator==(const iter_impl &other) const
+        {
+            // 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"));
+            }
+
+            assert(m_object != nullptr);
+
+            switch (m_object->m_type)
+            {
+            case basic_json::value_t::object:
+            {
+                return (m_it.object_iterator == other.m_it.object_iterator);
+            }
+
+            case basic_json::value_t::array:
+            {
+                return (m_it.array_iterator == other.m_it.array_iterator);
+            }
+
+            default:
+            {
+                return (m_it.primitive_iterator ==
+                        other.m_it.primitive_iterator);
+            }
+            }
+        }
+
+        /*!
+        @brief  comparison: not equal
+        @pre The iterator is initialized; i.e. `m_object != nullptr`.
+        */
+        bool operator!=(const iter_impl &other) const
+        {
+            return not operator==(other);
+        }
+
+        /*!
+        @brief  comparison: smaller
+        @pre The iterator is initialized; i.e. `m_object != nullptr`.
+        */
+        bool operator<(const iter_impl &other) const
+        {
+            // 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"));
+            }
+
+            assert(m_object != nullptr);
+
+            switch (m_object->m_type)
+            {
+            case basic_json::value_t::object:
+            {
+                JSON_THROW(std::domain_error(
+                    "cannot compare order of object iterators"));
+            }
+
+            case basic_json::value_t::array:
+            {
+                return (m_it.array_iterator < other.m_it.array_iterator);
+            }
+
+            default:
+            {
+                return (m_it.primitive_iterator <
+                        other.m_it.primitive_iterator);
+            }
+            }
+        }
+
+        /*!
+        @brief  comparison: less than or equal
+        @pre The iterator is initialized; i.e. `m_object != nullptr`.
+        */
+        bool operator<=(const iter_impl &other) const
+        {
+            return not other.operator<(*this);
+        }
+
+        /*!
+        @brief  comparison: greater than
+        @pre The iterator is initialized; i.e. `m_object != nullptr`.
+        */
+        bool operator>(const iter_impl &other) const
+        {
+            return not operator<=(other);
+        }
+
+        /*!
+        @brief  comparison: greater than or equal
+        @pre The iterator is initialized; i.e. `m_object != nullptr`.
+        */
+        bool operator>=(const iter_impl &other) const
+        {
+            return not operator<(other);
+        }
+
+        /*!
+        @brief  add to iterator
+        @pre The iterator is initialized; i.e. `m_object != nullptr`.
+        */
+        iter_impl &operator+=(difference_type i)
+        {
+            assert(m_object != nullptr);
+
+            switch (m_object->m_type)
+            {
+            case basic_json::value_t::object:
+            {
+                JSON_THROW(std::domain_error(
+                    "cannot use offsets with object iterators"));
+            }
+
+            case basic_json::value_t::array:
+            {
+                std::advance(m_it.array_iterator, i);
+                break;
+            }
+
+            default:
+            {
+                m_it.primitive_iterator += i;
+                break;
+            }
+            }
+
+            return *this;
+        }
+
+        /*!
+        @brief  subtract from iterator
+        @pre The iterator is initialized; i.e. `m_object != nullptr`.
+        */
+        iter_impl &operator-=(difference_type i) { return operator+=(-i); }
+
+        /*!
+        @brief  add to iterator
+        @pre The iterator is initialized; i.e. `m_object != nullptr`.
+        */
+        iter_impl operator+(difference_type i)
+        {
+            auto result = *this;
+            result += i;
+            return result;
+        }
+
+        /*!
+        @brief  subtract from iterator
+        @pre The iterator is initialized; i.e. `m_object != nullptr`.
+        */
+        iter_impl operator-(difference_type i)
+        {
+            auto result = *this;
+            result -= i;
+            return result;
+        }
+
+        /*!
+        @brief  return difference
+        @pre The iterator is initialized; i.e. `m_object != nullptr`.
+        */
+        difference_type operator-(const iter_impl &other) const
+        {
+            assert(m_object != nullptr);
+
+            switch (m_object->m_type)
+            {
+            case basic_json::value_t::object:
+            {
+                JSON_THROW(std::domain_error(
+                    "cannot use offsets with object iterators"));
+            }
+
+            case basic_json::value_t::array:
+            {
+                return m_it.array_iterator - other.m_it.array_iterator;
+            }
+
+            default:
+            {
+                return m_it.primitive_iterator - other.m_it.primitive_iterator;
+            }
+            }
+        }
+
+        /*!
+        @brief  access to successor
+        @pre The iterator is initialized; i.e. `m_object != nullptr`.
+        */
+        reference operator[](difference_type n) const
+        {
+            assert(m_object != nullptr);
+
+            switch (m_object->m_type)
+            {
+            case basic_json::value_t::object:
+            {
+                JSON_THROW(std::domain_error(
+                    "cannot use operator[] for object iterators"));
+            }
+
+            case basic_json::value_t::array:
+            {
+                return *std::next(m_it.array_iterator, n);
+            }
+
+            case basic_json::value_t::null:
+            {
+                JSON_THROW(std::out_of_range("cannot get value"));
+            }
+
+            default:
+            {
+                if (m_it.primitive_iterator == -n)
+                {
+                    return *m_object;
+                }
+
+                JSON_THROW(std::out_of_range("cannot get value"));
+            }
+            }
+        }
+
+        /*!
+        @brief  return the key of an object iterator
+        @pre The iterator is initialized; i.e. `m_object != nullptr`.
+        */
+        typename object_t::key_type key() const
+        {
+            assert(m_object != nullptr);
+
+            if (m_object->is_object())
+            {
+                return m_it.object_iterator->first;
+            }
+
+            JSON_THROW(
+                std::domain_error("cannot use key() for non-object iterators"));
+        }
+
+        /*!
+        @brief  return the value of an iterator
+        @pre The iterator is initialized; i.e. `m_object != nullptr`.
+        */
+        reference value() const { return operator*(); }
+
+    private:
+        /// associated JSON instance
+        pointer m_object = nullptr;
+        /// the actual iterator of the associated instance
+        internal_iterator m_it = internal_iterator();
+    };
+
+    /*!
+    @brief a template for a reverse iterator class
+
+    @tparam Base the base iterator type to reverse. Valid types are @ref
+    iterator (to create @ref reverse_iterator) and @ref const_iterator (to
+    create @ref const_reverse_iterator).
+
+    @requirement The class satisfies the following concept requirements:
+    -
+    [RandomAccessIterator](http://en.cppreference.com/w/cpp/concept/RandomAccessIterator):
+      The iterator that can be moved to point (forward and backward) to any
+      element in constant time.
+    - [OutputIterator](http://en.cppreference.com/w/cpp/concept/OutputIterator):
+      It is possible to write to the pointed-to element (only if @a Base is
+      @ref iterator).
+
+    @since version 1.0.0
+    */
+    template <typename Base>
+    class json_reverse_iterator : public std::reverse_iterator<Base>
+    {
+    public:
+        /// shortcut to the reverse iterator adaptor
+        using base_iterator = std::reverse_iterator<Base>;
+        /// the reference type for the pointed-to element
+        using reference = typename Base::reference;
+
+        /// create reverse iterator from iterator
+        json_reverse_iterator(
+            const typename base_iterator::iterator_type &it) noexcept
+            : base_iterator(it)
+        {
+        }
+
+        /// create reverse iterator from base class
+        json_reverse_iterator(const base_iterator &it) noexcept
+            : base_iterator(it)
+        {
+        }
+
+        /// post-increment (it++)
+        json_reverse_iterator operator++(int)
+        {
+            return base_iterator::operator++(1);
+        }
+
+        /// pre-increment (++it)
+        json_reverse_iterator &operator++()
+        {
+            base_iterator::operator++();
+            return *this;
+        }
+
+        /// post-decrement (it--)
+        json_reverse_iterator operator--(int)
+        {
+            return base_iterator::operator--(1);
+        }
+
+        /// pre-decrement (--it)
+        json_reverse_iterator &operator--()
+        {
+            base_iterator::operator--();
+            return *this;
+        }
+
+        /// add to iterator
+        json_reverse_iterator &operator+=(difference_type i)
+        {
+            base_iterator::operator+=(i);
+            return *this;
+        }
+
+        /// add to iterator
+        json_reverse_iterator operator+(difference_type i) const
+        {
+            auto result = *this;
+            result += i;
+            return result;
+        }
+
+        /// subtract from iterator
+        json_reverse_iterator operator-(difference_type i) const
+        {
+            auto result = *this;
+            result -= i;
+            return result;
+        }
+
+        /// return difference
+        difference_type operator-(const json_reverse_iterator &other) const
+        {
+            return this->base() - other.base();
+        }
+
+        /// access to successor
+        reference operator[](difference_type n) const
+        {
+            return *(this->operator+(n));
+        }
+
+        /// return the key of an object iterator
+        typename object_t::key_type key() const
+        {
+            auto it = --this->base();
+            return it.key();
+        }
+
+        /// return the value of an iterator
+        reference value() const
+        {
+            auto it = --this->base();
+            return it.operator*();
+        }
+    };
+
+private:
+    //////////////////////
+    // lexer and parser //
+    //////////////////////
+
+    /*!
+    @brief lexical analysis
+
+    This class organizes the lexical analysis during JSON deserialization. The
+    core of it is a scanner generated by [re2c](http://re2c.org) that
+    processes a buffer and recognizes tokens according to RFC 7159.
+    */
+    class lexer
+    {
+    public:
+        /// 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 `}`
+            name_separator,  ///< the name separator `:`
+            value_separator, ///< the value separator `,`
+            parse_error,     ///< indicating a parse error
+            end_of_input     ///< indicating the end of the input buffer
+        };
+
+        /// the char type to use in the lexer
+        using lexer_char_t = unsigned char;
+
+        /// a lexer from a buffer with given length
+        lexer(const lexer_char_t *buff, const size_t len) noexcept
+            : m_content(buff)
+        {
+            assert(m_content != nullptr);
+            m_start = m_cursor = m_content;
+            m_limit = m_content + len;
+        }
+
+        /// a lexer from an input stream
+        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"));
+            }
+
+            // fill buffer
+            fill_line_buffer();
+
+            // skip UTF-8 byte-order mark
+            if (m_line_buffer.size() >= 3 and
+                m_line_buffer.substr(0, 3) == "\xEF\xBB\xBF")
+            {
+                m_line_buffer[0] = ' ';
+                m_line_buffer[1] = ' ';
+                m_line_buffer[2] = ' ';
+            }
+        }
+
+        // switch off unwanted functions (due to pointer members)
+        lexer() = delete;
+        lexer(const lexer &) = delete;
+        lexer operator=(const lexer &) = delete;
+
+        /*!
+        @brief create a string from one or two Unicode code points
+
+        There are two cases: (1) @a codepoint1 is in the Basic Multilingual
+        Plane (U+0000 through U+FFFF) and @a codepoint2 is 0, or (2)
+        @a codepoint1 and @a codepoint2 are a UTF-16 surrogate pair to
+        represent a code point above U+FFFF.
+
+        @param[in] codepoint1  the code point (can be high surrogate)
+        @param[in] codepoint2  the code point (can be low surrogate or 0)
+
+        @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:
+        `""missing or wrong low surrogate""`
+
+        @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)
+        {
+            // calculate the code point from the given code points
+            std::size_t codepoint = codepoint1;
+
+            // check if codepoint1 is a high surrogate
+            if (codepoint1 >= 0xD800 and codepoint1 <= 0xDBFF)
+            {
+                // check if codepoint2 is a low surrogate
+                if (codepoint2 >= 0xDC00 and codepoint2 <= 0xDFFF)
+                {
+                    codepoint =
+                        // high surrogate occupies the most significant 22 bits
+                        (codepoint1 << 10)
+                        // low surrogate occupies the least significant 15 bits
+                        + codepoint2
+                        // there is still the 0xD800, 0xDC00 and 0x10000 noise
+                        // in the result so we have to subtract with:
+                        // (0xD800 << 10) + DC00 - 0x10000 = 0x35FDC00
+                        - 0x35FDC00;
+                }
+                else
+                {
+                    JSON_THROW(std::invalid_argument(
+                        "missing or wrong low surrogate"));
+                }
+            }
+
+            string_t result;
+
+            if (codepoint < 0x80)
+            {
+                // 1-byte characters: 0xxxxxxx (ASCII)
+                result.append(
+                    1, static_cast<typename string_t::value_type>(codepoint));
+            }
+            else if (codepoint <= 0x7ff)
+            {
+                // 2-byte characters: 110xxxxx 10xxxxxx
+                result.append(1, static_cast<typename string_t::value_type>(
+                                     0xC0 | ((codepoint >> 6) & 0x1F)));
+                result.append(1, static_cast<typename string_t::value_type>(
+                                     0x80 | (codepoint & 0x3F)));
+            }
+            else if (codepoint <= 0xffff)
+            {
+                // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx
+                result.append(1, static_cast<typename string_t::value_type>(
+                                     0xE0 | ((codepoint >> 12) & 0x0F)));
+                result.append(1, static_cast<typename string_t::value_type>(
+                                     0x80 | ((codepoint >> 6) & 0x3F)));
+                result.append(1, static_cast<typename string_t::value_type>(
+                                     0x80 | (codepoint & 0x3F)));
+            }
+            else if (codepoint <= 0x10ffff)
+            {
+                // 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+                result.append(1, static_cast<typename string_t::value_type>(
+                                     0xF0 | ((codepoint >> 18) & 0x07)));
+                result.append(1, static_cast<typename string_t::value_type>(
+                                     0x80 | ((codepoint >> 12) & 0x3F)));
+                result.append(1, static_cast<typename string_t::value_type>(
+                                     0x80 | ((codepoint >> 6) & 0x3F)));
+                result.append(1, static_cast<typename string_t::value_type>(
+                                     0x80 | (codepoint & 0x3F)));
+            }
+            else
+            {
+                JSON_THROW(std::out_of_range(
+                    "code points above 0x10FFFF are invalid"));
+            }
+
+            return result;
+        }
+
+        /// return name of values of type token_type (only used for errors)
+        static std::string token_type_name(const token_type t)
+        {
+            switch (t)
+            {
+            case token_type::uninitialized:
+                return "<uninitialized>";
+            case token_type::literal_true:
+                return "true literal";
+            case token_type::literal_false:
+                return "false literal";
+            case token_type::literal_null:
+                return "null literal";
+            case token_type::value_string:
+                return "string literal";
+            case token_type::value_number:
+                return "number literal";
+            case token_type::begin_array:
+                return "'['";
+            case token_type::begin_object:
+                return "'{'";
+            case token_type::end_array:
+                return "']'";
+            case token_type::end_object:
+                return "'}'";
+            case token_type::name_separator:
+                return "':'";
+            case token_type::value_separator:
+                return "','";
+            case token_type::parse_error:
+                return "<parse error>";
+            case token_type::end_of_input:
+                return "end of input";
+            default:
+            {
+                // catch non-enum values
+                return "unknown token"; // LCOV_EXCL_LINE
+            }
+            }
+        }
+
+        /*!
+        This function implements a scanner for JSON. It is specified using
+        regular expressions that try to follow RFC 7159 as close as possible.
+        These regular expressions are then translated into a minimized
+        deterministic finite automaton (DFA) by the tool
+        [re2c](http://re2c.org). As a result, the translated code for this
+        function consists of a large block of code with `goto` jumps.
+
+        @return the class of the next token read from the buffer
+
+        @complexity Linear in the length of the input.\n
+
+        Proposition: The loop below will always terminate for finite input.\n
+
+        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.
+        */
+        token_type scan()
+        {
+            while (true)
+            {
+                // pointer for backtracking information
+                m_marker = nullptr;
+
+                // remember the begin of the token
+                m_start = m_cursor;
+                assert(m_start != nullptr);
+
+                {
+                    lexer_char_t yych;
+                    unsigned int yyaccept = 0;
+                    static const unsigned char yybm[] = {
+                        0,   0,   0,   0,   0,   0,   0,   0,   0,   32,  32,
+                        0,   0,   32,  0,   0,   0,   0,   0,   0,   0,   0,
+                        0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   160,
+                        128, 0,   128, 128, 128, 128, 128, 128, 128, 128, 128,
+                        128, 128, 128, 128, 192, 192, 192, 192, 192, 192, 192,
+                        192, 192, 192, 128, 128, 128, 128, 128, 128, 128, 128,
+                        128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
+                        128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
+                        128, 128, 128, 128, 0,   128, 128, 128, 128, 128, 128,
+                        128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
+                        128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
+                        128, 128, 128, 128, 128, 128, 128, 0,   0,   0,   0,
+                        0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+                        0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+                        0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+                        0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+                        0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+                        0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+                        0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+                        0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+                        0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+                        0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+                        0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+                        0,   0,   0,
+                    };
+                    if ((m_limit - m_cursor) < 5)
+                    {
+                        fill_line_buffer(5); // LCOV_EXCL_LINE
+                    }
+                    yych = *m_cursor;
+                    if (yybm[0 + yych] & 32)
+                    {
+                        goto basic_json_parser_6;
+                    }
+                    if (yych <= '[')
+                    {
+                        if (yych <= '-')
+                        {
+                            if (yych <= '"')
+                            {
+                                if (yych <= 0x00)
+                                {
+                                    goto basic_json_parser_2;
+                                }
+                                if (yych <= '!')
+                                {
+                                    goto basic_json_parser_4;
+                                }
+                                goto basic_json_parser_9;
+                            }
+                            else
+                            {
+                                if (yych <= '+')
+                                {
+                                    goto basic_json_parser_4;
+                                }
+                                if (yych <= ',')
+                                {
+                                    goto basic_json_parser_10;
+                                }
+                                goto basic_json_parser_12;
+                            }
+                        }
+                        else
+                        {
+                            if (yych <= '9')
+                            {
+                                if (yych <= '/')
+                                {
+                                    goto basic_json_parser_4;
+                                }
+                                if (yych <= '0')
+                                {
+                                    goto basic_json_parser_13;
+                                }
+                                goto basic_json_parser_15;
+                            }
+                            else
+                            {
+                                if (yych <= ':')
+                                {
+                                    goto basic_json_parser_17;
+                                }
+                                if (yych <= 'Z')
+                                {
+                                    goto basic_json_parser_4;
+                                }
+                                goto basic_json_parser_19;
+                            }
+                        }
+                    }
+                    else
+                    {
+                        if (yych <= 'n')
+                        {
+                            if (yych <= 'e')
+                            {
+                                if (yych == ']')
+                                {
+                                    goto basic_json_parser_21;
+                                }
+                                goto basic_json_parser_4;
+                            }
+                            else
+                            {
+                                if (yych <= 'f')
+                                {
+                                    goto basic_json_parser_23;
+                                }
+                                if (yych <= 'm')
+                                {
+                                    goto basic_json_parser_4;
+                                }
+                                goto basic_json_parser_24;
+                            }
+                        }
+                        else
+                        {
+                            if (yych <= 'z')
+                            {
+                                if (yych == 't')
+                                {
+                                    goto basic_json_parser_25;
+                                }
+                                goto basic_json_parser_4;
+                            }
+                            else
+                            {
+                                if (yych <= '{')
+                                {
+                                    goto basic_json_parser_26;
+                                }
+                                if (yych == '}')
+                                {
+                                    goto basic_json_parser_28;
+                                }
+                                goto basic_json_parser_4;
+                            }
+                        }
+                    }
+                basic_json_parser_2:
+                    ++m_cursor;
+                    {
+                        last_token_type = token_type::end_of_input;
+                        break;
+                    }
+                basic_json_parser_4:
+                    ++m_cursor;
+                basic_json_parser_5:
+                {
+                    last_token_type = token_type::parse_error;
+                    break;
+                }
+                basic_json_parser_6:
+                    ++m_cursor;
+                    if (m_limit <= m_cursor)
+                    {
+                        fill_line_buffer(1); // LCOV_EXCL_LINE
+                    }
+                    yych = *m_cursor;
+                    if (yybm[0 + yych] & 32)
+                    {
+                        goto basic_json_parser_6;
+                    }
+                    {
+                        continue;
+                    }
+                basic_json_parser_9:
+                    yyaccept = 0;
+                    yych = *(m_marker = ++m_cursor);
+                    if (yych <= 0x1F)
+                    {
+                        goto basic_json_parser_5;
+                    }
+                    if (yych <= 0x7F)
+                    {
+                        goto basic_json_parser_31;
+                    }
+                    if (yych <= 0xC1)
+                    {
+                        goto basic_json_parser_5;
+                    }
+                    if (yych <= 0xF4)
+                    {
+                        goto basic_json_parser_31;
+                    }
+                    goto basic_json_parser_5;
+                basic_json_parser_10:
+                    ++m_cursor;
+                    {
+                        last_token_type = token_type::value_separator;
+                        break;
+                    }
+                basic_json_parser_12:
+                    yych = *++m_cursor;
+                    if (yych <= '/')
+                    {
+                        goto basic_json_parser_5;
+                    }
+                    if (yych <= '0')
+                    {
+                        goto basic_json_parser_13;
+                    }
+                    if (yych <= '9')
+                    {
+                        goto basic_json_parser_15;
+                    }
+                    goto basic_json_parser_5;
+                basic_json_parser_13:
+                    yyaccept = 1;
+                    yych = *(m_marker = ++m_cursor);
+                    if (yych <= 'D')
+                    {
+                        if (yych == '.')
+                        {
+                            goto basic_json_parser_43;
+                        }
+                    }
+                    else
+                    {
+                        if (yych <= 'E')
+                        {
+                            goto basic_json_parser_44;
+                        }
+                        if (yych == 'e')
+                        {
+                            goto basic_json_parser_44;
+                        }
+                    }
+                basic_json_parser_14:
+                {
+                    last_token_type = token_type::value_number;
+                    break;
+                }
+                basic_json_parser_15:
+                    yyaccept = 1;
+                    m_marker = ++m_cursor;
+                    if ((m_limit - m_cursor) < 3)
+                    {
+                        fill_line_buffer(3); // LCOV_EXCL_LINE
+                    }
+                    yych = *m_cursor;
+                    if (yybm[0 + yych] & 64)
+                    {
+                        goto basic_json_parser_15;
+                    }
+                    if (yych <= 'D')
+                    {
+                        if (yych == '.')
+                        {
+                            goto basic_json_parser_43;
+                        }
+                        goto basic_json_parser_14;
+                    }
+                    else
+                    {
+                        if (yych <= 'E')
+                        {
+                            goto basic_json_parser_44;
+                        }
+                        if (yych == 'e')
+                        {
+                            goto basic_json_parser_44;
+                        }
+                        goto basic_json_parser_14;
+                    }
+                basic_json_parser_17:
+                    ++m_cursor;
+                    {
+                        last_token_type = token_type::name_separator;
+                        break;
+                    }
+                basic_json_parser_19:
+                    ++m_cursor;
+                    {
+                        last_token_type = token_type::begin_array;
+                        break;
+                    }
+                basic_json_parser_21:
+                    ++m_cursor;
+                    {
+                        last_token_type = token_type::end_array;
+                        break;
+                    }
+                basic_json_parser_23:
+                    yyaccept = 0;
+                    yych = *(m_marker = ++m_cursor);
+                    if (yych == 'a')
+                    {
+                        goto basic_json_parser_45;
+                    }
+                    goto basic_json_parser_5;
+                basic_json_parser_24:
+                    yyaccept = 0;
+                    yych = *(m_marker = ++m_cursor);
+                    if (yych == 'u')
+                    {
+                        goto basic_json_parser_46;
+                    }
+                    goto basic_json_parser_5;
+                basic_json_parser_25:
+                    yyaccept = 0;
+                    yych = *(m_marker = ++m_cursor);
+                    if (yych == 'r')
+                    {
+                        goto basic_json_parser_47;
+                    }
+                    goto basic_json_parser_5;
+                basic_json_parser_26:
+                    ++m_cursor;
+                    {
+                        last_token_type = token_type::begin_object;
+                        break;
+                    }
+                basic_json_parser_28:
+                    ++m_cursor;
+                    {
+                        last_token_type = token_type::end_object;
+                        break;
+                    }
+                basic_json_parser_30:
+                    ++m_cursor;
+                    if (m_limit <= m_cursor)
+                    {
+                        fill_line_buffer(1); // LCOV_EXCL_LINE
+                    }
+                    yych = *m_cursor;
+                basic_json_parser_31:
+                    if (yybm[0 + yych] & 128)
+                    {
+                        goto basic_json_parser_30;
+                    }
+                    if (yych <= 0xE0)
+                    {
+                        if (yych <= '\\')
+                        {
+                            if (yych <= 0x1F)
+                            {
+                                goto basic_json_parser_32;
+                            }
+                            if (yych <= '"')
+                            {
+                                goto basic_json_parser_33;
+                            }
+                            goto basic_json_parser_35;
+                        }
+                        else
+                        {
+                            if (yych <= 0xC1)
+                            {
+                                goto basic_json_parser_32;
+                            }
+                            if (yych <= 0xDF)
+                            {
+                                goto basic_json_parser_36;
+                            }
+                            goto basic_json_parser_37;
+                        }
+                    }
+                    else
+                    {
+                        if (yych <= 0xEF)
+                        {
+                            if (yych == 0xED)
+                            {
+                                goto basic_json_parser_39;
+                            }
+                            goto basic_json_parser_38;
+                        }
+                        else
+                        {
+                            if (yych <= 0xF0)
+                            {
+                                goto basic_json_parser_40;
+                            }
+                            if (yych <= 0xF3)
+                            {
+                                goto basic_json_parser_41;
+                            }
+                            if (yych <= 0xF4)
+                            {
+                                goto basic_json_parser_42;
+                            }
+                        }
+                    }
+                basic_json_parser_32:
+                    m_cursor = m_marker;
+                    if (yyaccept == 0)
+                    {
+                        goto basic_json_parser_5;
+                    }
+                    else
+                    {
+                        goto basic_json_parser_14;
+                    }
+                basic_json_parser_33:
+                    ++m_cursor;
+                    {
+                        last_token_type = token_type::value_string;
+                        break;
+                    }
+                basic_json_parser_35:
+                    ++m_cursor;
+                    if (m_limit <= m_cursor)
+                    {
+                        fill_line_buffer(1); // LCOV_EXCL_LINE
+                    }
+                    yych = *m_cursor;
+                    if (yych <= 'e')
+                    {
+                        if (yych <= '/')
+                        {
+                            if (yych == '"')
+                            {
+                                goto basic_json_parser_30;
+                            }
+                            if (yych <= '.')
+                            {
+                                goto basic_json_parser_32;
+                            }
+                            goto basic_json_parser_30;
+                        }
+                        else
+                        {
+                            if (yych <= '\\')
+                            {
+                                if (yych <= '[')
+                                {
+                                    goto basic_json_parser_32;
+                                }
+                                goto basic_json_parser_30;
+                            }
+                            else
+                            {
+                                if (yych == 'b')
+                                {
+                                    goto basic_json_parser_30;
+                                }
+                                goto basic_json_parser_32;
+                            }
+                        }
+                    }
+                    else
+                    {
+                        if (yych <= 'q')
+                        {
+                            if (yych <= 'f')
+                            {
+                                goto basic_json_parser_30;
+                            }
+                            if (yych == 'n')
+                            {
+                                goto basic_json_parser_30;
+                            }
+                            goto basic_json_parser_32;
+                        }
+                        else
+                        {
+                            if (yych <= 's')
+                            {
+                                if (yych <= 'r')
+                                {
+                                    goto basic_json_parser_30;
+                                }
+                                goto basic_json_parser_32;
+                            }
+                            else
+                            {
+                                if (yych <= 't')
+                                {
+                                    goto basic_json_parser_30;
+                                }
+                                if (yych <= 'u')
+                                {
+                                    goto basic_json_parser_48;
+                                }
+                                goto basic_json_parser_32;
+                            }
+                        }
+                    }
+                basic_json_parser_36:
+                    ++m_cursor;
+                    if (m_limit <= m_cursor)
+                    {
+                        fill_line_buffer(1); // LCOV_EXCL_LINE
+                    }
+                    yych = *m_cursor;
+                    if (yych <= 0x7F)
+                    {
+                        goto basic_json_parser_32;
+                    }
+                    if (yych <= 0xBF)
+                    {
+                        goto basic_json_parser_30;
+                    }
+                    goto basic_json_parser_32;
+                basic_json_parser_37:
+                    ++m_cursor;
+                    if (m_limit <= m_cursor)
+                    {
+                        fill_line_buffer(1); // LCOV_EXCL_LINE
+                    }
+                    yych = *m_cursor;
+                    if (yych <= 0x9F)
+                    {
+                        goto basic_json_parser_32;
+                    }
+                    if (yych <= 0xBF)
+                    {
+                        goto basic_json_parser_36;
+                    }
+                    goto basic_json_parser_32;
+                basic_json_parser_38:
+                    ++m_cursor;
+                    if (m_limit <= m_cursor)
+                    {
+                        fill_line_buffer(1); // LCOV_EXCL_LINE
+                    }
+                    yych = *m_cursor;
+                    if (yych <= 0x7F)
+                    {
+                        goto basic_json_parser_32;
+                    }
+                    if (yych <= 0xBF)
+                    {
+                        goto basic_json_parser_36;
+                    }
+                    goto basic_json_parser_32;
+                basic_json_parser_39:
+                    ++m_cursor;
+                    if (m_limit <= m_cursor)
+                    {
+                        fill_line_buffer(1); // LCOV_EXCL_LINE
+                    }
+                    yych = *m_cursor;
+                    if (yych <= 0x7F)
+                    {
+                        goto basic_json_parser_32;
+                    }
+                    if (yych <= 0x9F)
+                    {
+                        goto basic_json_parser_36;
+                    }
+                    goto basic_json_parser_32;
+                basic_json_parser_40:
+                    ++m_cursor;
+                    if (m_limit <= m_cursor)
+                    {
+                        fill_line_buffer(1); // LCOV_EXCL_LINE
+                    }
+                    yych = *m_cursor;
+                    if (yych <= 0x8F)
+                    {
+                        goto basic_json_parser_32;
+                    }
+                    if (yych <= 0xBF)
+                    {
+                        goto basic_json_parser_38;
+                    }
+                    goto basic_json_parser_32;
+                basic_json_parser_41:
+                    ++m_cursor;
+                    if (m_limit <= m_cursor)
+                    {
+                        fill_line_buffer(1); // LCOV_EXCL_LINE
+                    }
+                    yych = *m_cursor;
+                    if (yych <= 0x7F)
+                    {
+                        goto basic_json_parser_32;
+                    }
+                    if (yych <= 0xBF)
+                    {
+                        goto basic_json_parser_38;
+                    }
+                    goto basic_json_parser_32;
+                basic_json_parser_42:
+                    ++m_cursor;
+                    if (m_limit <= m_cursor)
+                    {
+                        fill_line_buffer(1); // LCOV_EXCL_LINE
+                    }
+                    yych = *m_cursor;
+                    if (yych <= 0x7F)
+                    {
+                        goto basic_json_parser_32;
+                    }
+                    if (yych <= 0x8F)
+                    {
+                        goto basic_json_parser_38;
+                    }
+                    goto basic_json_parser_32;
+                basic_json_parser_43:
+                    yych = *++m_cursor;
+                    if (yych <= '/')
+                    {
+                        goto basic_json_parser_32;
+                    }
+                    if (yych <= '9')
+                    {
+                        goto basic_json_parser_49;
+                    }
+                    goto basic_json_parser_32;
+                basic_json_parser_44:
+                    yych = *++m_cursor;
+                    if (yych <= ',')
+                    {
+                        if (yych == '+')
+                        {
+                            goto basic_json_parser_51;
+                        }
+                        goto basic_json_parser_32;
+                    }
+                    else
+                    {
+                        if (yych <= '-')
+                        {
+                            goto basic_json_parser_51;
+                        }
+                        if (yych <= '/')
+                        {
+                            goto basic_json_parser_32;
+                        }
+                        if (yych <= '9')
+                        {
+                            goto basic_json_parser_52;
+                        }
+                        goto basic_json_parser_32;
+                    }
+                basic_json_parser_45:
+                    yych = *++m_cursor;
+                    if (yych == 'l')
+                    {
+                        goto basic_json_parser_54;
+                    }
+                    goto basic_json_parser_32;
+                basic_json_parser_46:
+                    yych = *++m_cursor;
+                    if (yych == 'l')
+                    {
+                        goto basic_json_parser_55;
+                    }
+                    goto basic_json_parser_32;
+                basic_json_parser_47:
+                    yych = *++m_cursor;
+                    if (yych == 'u')
+                    {
+                        goto basic_json_parser_56;
+                    }
+                    goto basic_json_parser_32;
+                basic_json_parser_48:
+                    ++m_cursor;
+                    if (m_limit <= m_cursor)
+                    {
+                        fill_line_buffer(1); // LCOV_EXCL_LINE
+                    }
+                    yych = *m_cursor;
+                    if (yych <= '@')
+                    {
+                        if (yych <= '/')
+                        {
+                            goto basic_json_parser_32;
+                        }
+                        if (yych <= '9')
+                        {
+                            goto basic_json_parser_57;
+                        }
+                        goto basic_json_parser_32;
+                    }
+                    else
+                    {
+                        if (yych <= 'F')
+                        {
+                            goto basic_json_parser_57;
+                        }
+                        if (yych <= '`')
+                        {
+                            goto basic_json_parser_32;
+                        }
+                        if (yych <= 'f')
+                        {
+                            goto basic_json_parser_57;
+                        }
+                        goto basic_json_parser_32;
+                    }
+                basic_json_parser_49:
+                    yyaccept = 1;
+                    m_marker = ++m_cursor;
+                    if ((m_limit - m_cursor) < 3)
+                    {
+                        fill_line_buffer(3); // LCOV_EXCL_LINE
+                    }
+                    yych = *m_cursor;
+                    if (yych <= 'D')
+                    {
+                        if (yych <= '/')
+                        {
+                            goto basic_json_parser_14;
+                        }
+                        if (yych <= '9')
+                        {
+                            goto basic_json_parser_49;
+                        }
+                        goto basic_json_parser_14;
+                    }
+                    else
+                    {
+                        if (yych <= 'E')
+                        {
+                            goto basic_json_parser_44;
+                        }
+                        if (yych == 'e')
+                        {
+                            goto basic_json_parser_44;
+                        }
+                        goto basic_json_parser_14;
+                    }
+                basic_json_parser_51:
+                    yych = *++m_cursor;
+                    if (yych <= '/')
+                    {
+                        goto basic_json_parser_32;
+                    }
+                    if (yych >= ':')
+                    {
+                        goto basic_json_parser_32;
+                    }
+                basic_json_parser_52:
+                    ++m_cursor;
+                    if (m_limit <= m_cursor)
+                    {
+                        fill_line_buffer(1); // LCOV_EXCL_LINE
+                    }
+                    yych = *m_cursor;
+                    if (yych <= '/')
+                    {
+                        goto basic_json_parser_14;
+                    }
+                    if (yych <= '9')
+                    {
+                        goto basic_json_parser_52;
+                    }
+                    goto basic_json_parser_14;
+                basic_json_parser_54:
+                    yych = *++m_cursor;
+                    if (yych == 's')
+                    {
+                        goto basic_json_parser_58;
+                    }
+                    goto basic_json_parser_32;
+                basic_json_parser_55:
+                    yych = *++m_cursor;
+                    if (yych == 'l')
+                    {
+                        goto basic_json_parser_59;
+                    }
+                    goto basic_json_parser_32;
+                basic_json_parser_56:
+                    yych = *++m_cursor;
+                    if (yych == 'e')
+                    {
+                        goto basic_json_parser_61;
+                    }
+                    goto basic_json_parser_32;
+                basic_json_parser_57:
+                    ++m_cursor;
+                    if (m_limit <= m_cursor)
+                    {
+                        fill_line_buffer(1); // LCOV_EXCL_LINE
+                    }
+                    yych = *m_cursor;
+                    if (yych <= '@')
+                    {
+                        if (yych <= '/')
+                        {
+                            goto basic_json_parser_32;
+                        }
+                        if (yych <= '9')
+                        {
+                            goto basic_json_parser_63;
+                        }
+                        goto basic_json_parser_32;
+                    }
+                    else
+                    {
+                        if (yych <= 'F')
+                        {
+                            goto basic_json_parser_63;
+                        }
+                        if (yych <= '`')
+                        {
+                            goto basic_json_parser_32;
+                        }
+                        if (yych <= 'f')
+                        {
+                            goto basic_json_parser_63;
+                        }
+                        goto basic_json_parser_32;
+                    }
+                basic_json_parser_58:
+                    yych = *++m_cursor;
+                    if (yych == 'e')
+                    {
+                        goto basic_json_parser_64;
+                    }
+                    goto basic_json_parser_32;
+                basic_json_parser_59:
+                    ++m_cursor;
+                    {
+                        last_token_type = token_type::literal_null;
+                        break;
+                    }
+                basic_json_parser_61:
+                    ++m_cursor;
+                    {
+                        last_token_type = token_type::literal_true;
+                        break;
+                    }
+                basic_json_parser_63:
+                    ++m_cursor;
+                    if (m_limit <= m_cursor)
+                    {
+                        fill_line_buffer(1); // LCOV_EXCL_LINE
+                    }
+                    yych = *m_cursor;
+                    if (yych <= '@')
+                    {
+                        if (yych <= '/')
+                        {
+                            goto basic_json_parser_32;
+                        }
+                        if (yych <= '9')
+                        {
+                            goto basic_json_parser_66;
+                        }
+                        goto basic_json_parser_32;
+                    }
+                    else
+                    {
+                        if (yych <= 'F')
+                        {
+                            goto basic_json_parser_66;
+                        }
+                        if (yych <= '`')
+                        {
+                            goto basic_json_parser_32;
+                        }
+                        if (yych <= 'f')
+                        {
+                            goto basic_json_parser_66;
+                        }
+                        goto basic_json_parser_32;
+                    }
+                basic_json_parser_64:
+                    ++m_cursor;
+                    {
+                        last_token_type = token_type::literal_false;
+                        break;
+                    }
+                basic_json_parser_66:
+                    ++m_cursor;
+                    if (m_limit <= m_cursor)
+                    {
+                        fill_line_buffer(1); // LCOV_EXCL_LINE
+                    }
+                    yych = *m_cursor;
+                    if (yych <= '@')
+                    {
+                        if (yych <= '/')
+                        {
+                            goto basic_json_parser_32;
+                        }
+                        if (yych <= '9')
+                        {
+                            goto basic_json_parser_30;
+                        }
+                        goto basic_json_parser_32;
+                    }
+                    else
+                    {
+                        if (yych <= 'F')
+                        {
+                            goto basic_json_parser_30;
+                        }
+                        if (yych <= '`')
+                        {
+                            goto basic_json_parser_32;
+                        }
+                        if (yych <= 'f')
+                        {
+                            goto basic_json_parser_30;
+                        }
+                        goto basic_json_parser_32;
+                    }
+                }
+            }
+
+            return last_token_type;
+        }
+
+        /*!
+        @brief append data from the stream to the line buffer
+
+        This function is called by the scan() function when the end of the
+        buffer (`m_limit`) is reached and the `m_cursor` pointer cannot be
+        incremented without leaving the limits of the line buffer. Note re2c
+        decides when to call this function.
+
+        If the lexer reads from contiguous storage, there is no trailing null
+        byte. Therefore, this function must make sure to add these padding
+        null bytes.
+
+        If the lexer reads from an input stream, this function reads the next
+        line of the input.
+
+        @pre
+            p p p p p p u u u u u x . . . . . .
+            ^           ^       ^   ^
+            m_content   m_start |   m_limit
+                                m_cursor
+
+        @post
+            u u u u u x x x x x x x . . . . . .
+            ^       ^               ^
+            |       m_cursor        m_limit
+            m_start
+            m_content
+        */
+        void fill_line_buffer(size_t n = 0)
+        {
+            // if line buffer is used, m_content points to its data
+            assert(m_line_buffer.empty() or
+                   m_content == reinterpret_cast<const lexer_char_t *>(
+                                    m_line_buffer.data()));
+
+            // if line buffer is used, m_limit is set past the end of its data
+            assert(m_line_buffer.empty() or
+                   m_limit == m_content + m_line_buffer.size());
+
+            // pointer relationships
+            assert(m_content <= m_start);
+            assert(m_start <= m_cursor);
+            assert(m_cursor <= m_limit);
+            assert(m_marker == nullptr or m_marker <= m_limit);
+
+            // number of processed characters (p)
+            const auto num_processed_chars =
+                static_cast<size_t>(m_start - m_content);
+            // offset for m_marker wrt. to m_start
+            const auto offset_marker =
+                (m_marker == nullptr) ? 0 : m_marker - m_start;
+            // number of unprocessed characters (u)
+            const auto offset_cursor = m_cursor - m_start;
+
+            // no stream is used or end of file is reached
+            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
+                // thing. See http://stackoverflow.com/q/28142011/266378
+                m_line_buffer.assign(m_start, m_limit);
+
+                // append n characters to make sure that there is sufficient
+                // space between m_cursor and m_limit
+                m_line_buffer.append(1, '\x00');
+                if (n > 0)
+                {
+                    m_line_buffer.append(n - 1, '\x01');
+                }
+            }
+            else
+            {
+                // delete processed characters from line buffer
+                m_line_buffer.erase(0, num_processed_chars);
+                // read next line from input stream
+                m_line_buffer_tmp.clear();
+                std::getline(*m_stream, m_line_buffer_tmp, '\n');
+
+                // add line with newline symbol to the line buffer
+                m_line_buffer += m_line_buffer_tmp;
+                m_line_buffer.push_back('\n');
+            }
+
+            // set pointers
+            m_content =
+                reinterpret_cast<const lexer_char_t *>(m_line_buffer.data());
+            assert(m_content != nullptr);
+            m_start = m_content;
+            m_marker = m_start + offset_marker;
+            m_cursor = m_start + offset_cursor;
+            m_limit = m_start + m_line_buffer.size();
+        }
+
+        /// return string representation of last read token
+        string_t get_token_string() const
+        {
+            assert(m_start != nullptr);
+            return string_t(
+                reinterpret_cast<typename string_t::const_pointer>(m_start),
+                static_cast<size_t>(m_cursor - m_start));
+        }
+
+        /*!
+        @brief return string value for string tokens
+
+        The function iterates the characters between the opening and closing
+        quotes of the string value. The complete string is the range
+        [m_start,m_cursor). Consequently, we iterate from m_start+1 to
+        m_cursor-1.
+
+        We differentiate two cases:
+
+        1. Escaped characters. In this case, a new character is constructed
+           according to the nature of the escape. Some escapes create new
+           characters (e.g., `"\\n"` is replaced by `"\n"`), some are copied
+           as is (e.g., `"\\\\"`). Furthermore, Unicode escapes of the shape
+           `"\\uxxxx"` need special care. In this case, to_unicode takes care
+           of the construction of the values.
+        2. Unescaped characters are copied as is.
+
+        @pre `m_cursor - m_start >= 2`, meaning the length of the last token
+        is at least 2 bytes which is trivially true for any string (which
+        consists of at least two quotes).
+
+            " c1 c2 c3 ... "
+            ^                ^
+            m_start          m_cursor
+
+        @complexity Linear in the length of the string.\n
+
+        Lemma: The loop body will always terminate.\n
+
+        Proof (by contradiction): Assume the loop body does not terminate. As
+        the loop body does not contain another loop, one of the called
+        functions must never return. The called functions are `std::strtoul`
+        and to_unicode. Neither function can loop forever, so the loop body
+        will never loop forever which contradicts the assumption that the loop
+        body does not terminate, q.e.d.\n
+
+        Lemma: The loop condition for the for loop is eventually false.\n
+
+        Proof (by contradiction): Assume the loop does not terminate. Due to
+        the above lemma, this can only be due to a tautological loop
+        condition; that is, the loop condition i < m_cursor - 1 must always be
+        true. Let x be the change of i for any loop iteration. Then
+        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
+        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
+        contradicts the assumption that the loop condition is a tautology,
+        q.e.d.
+
+        @return string value of current token without opening and closing
+        quotes
+        @throw std::out_of_range if to_unicode fails
+        */
+        string_t get_string() const
+        {
+            assert(m_cursor - m_start >= 2);
+
+            string_t result;
+            result.reserve(static_cast<size_t>(m_cursor - m_start - 2));
+
+            // iterate the result between the quotes
+            for (const lexer_char_t *i = m_start + 1; i < m_cursor - 1; ++i)
+            {
+                // find next escape character
+                auto e = std::find(i, m_cursor - 1, '\\');
+                if (e != i)
+                {
+                    // see
+                    // https://github.com/nlohmann/json/issues/365#issuecomment-262874705
+                    for (auto k = i; k < e; k++)
+                    {
+                        result.push_back(
+                            static_cast<typename string_t::value_type>(*k));
+                    }
+                    i = e - 1; // -1 because of ++i
+                }
+                else
+                {
+                    // processing escaped character
+                    // read next character
+                    ++i;
+
+                    switch (*i)
+                    {
+                    // the default escapes
+                    case 't':
+                    {
+                        result += "\t";
+                        break;
+                    }
+                    case 'b':
+                    {
+                        result += "\b";
+                        break;
+                    }
+                    case 'f':
+                    {
+                        result += "\f";
+                        break;
+                    }
+                    case 'n':
+                    {
+                        result += "\n";
+                        break;
+                    }
+                    case 'r':
+                    {
+                        result += "\r";
+                        break;
+                    }
+                    case '\\':
+                    {
+                        result += "\\";
+                        break;
+                    }
+                    case '/':
+                    {
+                        result += "/";
+                        break;
+                    }
+                    case '"':
+                    {
+                        result += "\"";
+                        break;
+                    }
+
+                    // unicode
+                    case 'u':
+                    {
+                        // get code xxxx from uxxxx
+                        auto codepoint = std::strtoul(
+                            std::string(
+                                reinterpret_cast<
+                                    typename string_t::const_pointer>(i + 1),
+                                4)
+                                .c_str(),
+                            nullptr, 16);
+
+                        // check if codepoint is a high surrogate
+                        if (codepoint >= 0xD800 and codepoint <= 0xDBFF)
+                        {
+                            // make sure there is a subsequent unicode
+                            if ((i + 6 >= m_limit) or *(i + 5) != '\\' or
+                                *(i + 6) != 'u')
+                            {
+                                JSON_THROW(std::invalid_argument(
+                                    "missing low surrogate"));
+                            }
+
+                            // get code yyyy from uxxxx\uyyyy
+                            auto codepoint2 = std::strtoul(
+                                std::string(
+                                    reinterpret_cast<
+                                        typename string_t::const_pointer>(i +
+                                                                          7),
+                                    4)
+                                    .c_str(),
+                                nullptr, 16);
+                            result += to_unicode(codepoint, codepoint2);
+                            // skip the next 10 characters (xxxx\uyyyy)
+                            i += 10;
+                        }
+                        else if (codepoint >= 0xDC00 and codepoint <= 0xDFFF)
+                        {
+                            // we found a lone low surrogate
+                            JSON_THROW(std::invalid_argument(
+                                "missing high surrogate"));
+                        }
+                        else
+                        {
+                            // add unicode character(s)
+                            result += to_unicode(codepoint);
+                            // skip the next four characters (xxxx)
+                            i += 4;
+                        }
+                        break;
+                    }
+                    }
+                }
+            }
+
+            return result;
+        }
+
+        /*!
+        @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).
+
+        @param[in,out] endptr recieves a pointer to the first character after
+        the number
+
+        @return the floating point number
+        */
+        long double str_to_float_t(long double * /* type */,
+                                   char **endptr) const
+        {
+            return std::strtold(
+                reinterpret_cast<typename string_t::const_pointer>(m_start),
+                endptr);
+        }
+
+        /*!
+        @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).
+
+        @param[in,out] endptr  recieves a pointer to the first character after
+        the number
+
+        @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);
+        }
+
+        /*!
+        @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).
+
+        @param[in,out] endptr  recieves a pointer to the first character after
+        the number
+
+        @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);
+        }
+
+        /*!
+        @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.
+
+        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.
+
+        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).
+
+        @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);
+
+            const lexer::lexer_char_t *curptr = m_start;
+
+            // accumulate the integer conversion result (unsigned for now)
+            number_unsigned_t value = 0;
+
+            // maximum absolute value of the relevant integer type
+            number_unsigned_t max;
+
+            // temporarily store the type to avoid unecessary bitfield access
+            value_t type;
+
+            // look for sign
+            if (*curptr == '-')
+            {
+                type = value_t::number_integer;
+                max = static_cast<uint64_t>(
+                          (std::numeric_limits<number_integer_t>::max)()) +
+                      1;
+                curptr++;
+            }
+            else
+            {
+                type = value_t::number_unsigned;
+                max = static_cast<uint64_t>(
+                    (std::numeric_limits<number_unsigned_t>::max)());
+            }
+
+            // count the significant figures
+            for (; curptr < m_cursor; curptr++)
+            {
+                // 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;
+                }
+
+                // skip if definitely not an integer
+                if (type != value_t::number_float)
+                {
+                    auto digit = static_cast<number_unsigned_t>(*curptr - '0');
+
+                    // 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;
+                    }
+                }
+            }
+
+            // save the value (if not a float)
+            if (type == value_t::number_unsigned)
+            {
+                result.m_value.number_unsigned = value;
+            }
+            else if (type == value_t::number_integer)
+            {
+                // 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)
+                {
+                    // 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);
+                }
+                else
+                {
+                    // all other values can be negated safely
+                    result.m_value.number_integer =
+                        -static_cast<number_integer_t>(value);
+                }
+            }
+            else
+            {
+                // parse with strtod
+                result.m_value.number_float = str_to_float_t(
+                    static_cast<number_float_t *>(nullptr), nullptr);
+
+                // replace infinity and NAN by null
+                if (not std::isfinite(result.m_value.number_float))
+                {
+                    type = value_t::null;
+                    result.m_value = basic_json::json_value();
+                }
+            }
+
+            // save the type
+            result.m_type = type;
+        }
+
+    private:
+        /// optional input stream
+        std::istream *m_stream = nullptr;
+        /// line buffer buffer for m_stream
+        string_t m_line_buffer{};
+        /// used for filling m_line_buffer
+        string_t m_line_buffer_tmp{};
+        /// the buffer pointer
+        const lexer_char_t *m_content = nullptr;
+        /// pointer to the beginning of the current symbol
+        const lexer_char_t *m_start = nullptr;
+        /// pointer for backtracking information
+        const lexer_char_t *m_marker = nullptr;
+        /// pointer to the current symbol
+        const lexer_char_t *m_cursor = nullptr;
+        /// pointer to the end of the buffer
+        const lexer_char_t *m_limit = nullptr;
+        /// the last token type
+        token_type last_token_type = token_type::end_of_input;
+    };
+
+    /*!
+    @brief syntax analysis
+
+    This class implements a recursive decent parser.
+    */
+    class parser
+    {
+    public:
+        /// a parser reading from a string literal
+        parser(const char *buff, const parser_callback_t cb = nullptr)
+        : callback(cb),
+          m_lexer(reinterpret_cast<const typename lexer::lexer_char_t *>(buff),
+                  std::strlen(buff))
+        {
+        }
+
+        /// a parser reading from an input stream
+        parser(std::istream &is, const parser_callback_t cb = nullptr)
+        : callback(cb), m_lexer(is)
+        {
+        }
+
+        /// a parser reading from an iterator range with contiguous storage
+        template <class IteratorType,
+                  typename std::enable_if<
+                      std::is_same<typename std::iterator_traits<
+                                       IteratorType>::iterator_category,
+                                   std::random_access_iterator_tag>::value,
+                      int>::type = 0>
+        parser(IteratorType first, IteratorType last,
+               const parser_callback_t cb = nullptr)
+        : callback(cb),
+          m_lexer(
+              reinterpret_cast<const typename lexer::lexer_char_t *>(&(*first)),
+              static_cast<size_t>(std::distance(first, last)))
+        {
+        }
+
+        /// public parser interface
+        basic_json parse()
+        {
+            // read first token
+            get_token();
+
+            basic_json result = parse_internal(true);
+            result.assert_invariant();
+
+            expect(lexer::token_type::end_of_input);
+
+            // return parser result and replace it with null in case the
+            // top-level value was discarded by the callback function
+            return result.is_discarded() ? basic_json() : std::move(result);
+        }
+
+    private:
+        /// the actual parser
+        basic_json parse_internal(bool keep)
+        {
+            auto result = basic_json(value_t::discarded);
+
+            switch (last_token)
+            {
+            case lexer::token_type::begin_object:
+            {
+                if (keep and
+                    (not callback or
+                     ((keep = callback(depth++, parse_event_t::object_start,
+                                       result)) != 0)))
+                {
+                    // explicitly set result to object to cope with {}
+                    result.m_type = value_t::object;
+                    result.m_value = value_t::object;
+                }
+
+                // read next token
+                get_token();
+
+                // closing } -> we are done
+                if (last_token == lexer::token_type::end_object)
+                {
+                    get_token();
+                    if (keep and callback and
+                        not callback(--depth, parse_event_t::object_end,
+                                     result))
+                    {
+                        result = basic_json(value_t::discarded);
+                    }
+                    return result;
+                }
+
+                // no comma is expected here
+                unexpect(lexer::token_type::value_separator);
+
+                // otherwise: parse key-value pairs
+                do
+                {
+                    // ugly, but could be fixed with loop reorganization
+                    if (last_token == lexer::token_type::value_separator)
+                    {
+                        get_token();
+                    }
+
+                    // store key
+                    expect(lexer::token_type::value_string);
+                    const auto key = m_lexer.get_string();
+
+                    bool keep_tag = false;
+                    if (keep)
+                    {
+                        if (callback)
+                        {
+                            basic_json k(key);
+                            keep_tag = callback(depth, parse_event_t::key, k);
+                        }
+                        else
+                        {
+                            keep_tag = true;
+                        }
+                    }
+
+                    // parse separator (:)
+                    get_token();
+                    expect(lexer::token_type::name_separator);
+
+                    // parse and add value
+                    get_token();
+                    auto value = parse_internal(keep);
+                    if (keep and keep_tag and not value.is_discarded())
+                    {
+                        result[key] = std::move(value);
+                    }
+                } while (last_token == lexer::token_type::value_separator);
+
+                // closing }
+                expect(lexer::token_type::end_object);
+                get_token();
+                if (keep and callback and
+                    not callback(--depth, parse_event_t::object_end, result))
+                {
+                    result = basic_json(value_t::discarded);
+                }
+
+                return result;
+            }
+
+            case lexer::token_type::begin_array:
+            {
+                if (keep and
+                    (not callback or
+                     ((keep = callback(depth++, parse_event_t::array_start,
+                                       result)) != 0)))
+                {
+                    // explicitly set result to object to cope with []
+                    result.m_type = value_t::array;
+                    result.m_value = value_t::array;
+                }
+
+                // read next token
+                get_token();
+
+                // closing ] -> we are done
+                if (last_token == lexer::token_type::end_array)
+                {
+                    get_token();
+                    if (callback and
+                        not callback(--depth, parse_event_t::array_end, result))
+                    {
+                        result = basic_json(value_t::discarded);
+                    }
+                    return result;
+                }
+
+                // no comma is expected here
+                unexpect(lexer::token_type::value_separator);
+
+                // otherwise: parse values
+                do
+                {
+                    // ugly, but could be fixed with loop reorganization
+                    if (last_token == lexer::token_type::value_separator)
+                    {
+                        get_token();
+                    }
+
+                    // parse value
+                    auto value = parse_internal(keep);
+                    if (keep and not value.is_discarded())
+                    {
+                        result.push_back(std::move(value));
+                    }
+                } while (last_token == lexer::token_type::value_separator);
+
+                // closing ]
+                expect(lexer::token_type::end_array);
+                get_token();
+                if (keep and callback and
+                    not callback(--depth, parse_event_t::array_end, result))
+                {
+                    result = basic_json(value_t::discarded);
+                }
+
+                return result;
+            }
+
+            case lexer::token_type::literal_null:
+            {
+                get_token();
+                result.m_type = value_t::null;
+                break;
+            }
+
+            case lexer::token_type::value_string:
+            {
+                const auto s = m_lexer.get_string();
+                get_token();
+                result = basic_json(s);
+                break;
+            }
+
+            case lexer::token_type::literal_true:
+            {
+                get_token();
+                result.m_type = value_t::boolean;
+                result.m_value = true;
+                break;
+            }
+
+            case lexer::token_type::literal_false:
+            {
+                get_token();
+                result.m_type = value_t::boolean;
+                result.m_value = false;
+                break;
+            }
+
+            case lexer::token_type::value_number:
+            {
+                m_lexer.get_number(result);
+                get_token();
+                break;
+            }
+
+            default:
+            {
+                // the last token was unexpected
+                unexpect(last_token);
+            }
+            }
+
+            if (keep and callback and
+                not callback(depth, parse_event_t::value, result))
+            {
+                result = basic_json(value_t::discarded);
+            }
+            return result;
+        }
+
+        /// get next token from lexer
+        typename lexer::token_type get_token()
+        {
+            last_token = m_lexer.scan();
+            return last_token;
+        }
+
+        void expect(typename lexer::token_type t) const
+        {
+            if (t != last_token)
+            {
+                std::string error_msg = "parse error - unexpected ";
+                error_msg += (last_token == lexer::token_type::parse_error
+                                  ? ("'" + 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));
+            }
+        }
+
+        void unexpect(typename lexer::token_type t) const
+        {
+            if (t == last_token)
+            {
+                std::string error_msg = "parse error - unexpected ";
+                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));
+            }
+        }
+
+    private:
+        /// current level of recursion
+        int depth = 0;
+        /// callback function
+        const parser_callback_t callback = nullptr;
+        /// the type of the last read token
+        typename lexer::token_type last_token =
+            lexer::token_type::uninitialized;
+        /// the lexer
+        lexer m_lexer;
+    };
+
+public:
+    /*!
+    @brief JSON Pointer
+
+    A JSON pointer defines a string syntax for identifying a specific value
+    within a JSON document. It can be used with functions `at` and
+    `operator[]`. Furthermore, JSON pointers are the base for JSON patches.
+
+    @sa [RFC 6901](https://tools.ietf.org/html/rfc6901)
+
+    @since version 2.0.0
+    */
+    class json_pointer
+    {
+        /// allow basic_json to access private members
+        friend class basic_json;
+
+    public:
+        /*!
+        @brief create JSON pointer
+
+        Create a JSON pointer according to the syntax described in
+        [Section 3 of RFC6901](https://tools.ietf.org/html/rfc6901#section-3).
+
+        @param[in] s  string representing the JSON pointer; if omitted, the
+                      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"`
+
+        @liveexample{The example shows the construction several valid JSON
+        pointers as well as the exceptional behavior.,json_pointer}
+
+        @since version 2.0.0
+        */
+        explicit json_pointer(const std::string &s = "")
+        : reference_tokens(split(s))
+        {
+        }
+
+        /*!
+        @brief return a string representation of the JSON pointer
+
+        @invariant For each JSON pointer `ptr`, it holds:
+        @code {.cpp}
+        ptr == json_pointer(ptr.to_string());
+        @endcode
+
+        @return a string representation of the JSON pointer
+
+        @liveexample{The example shows the result of `to_string`.,
+        json_pointer__to_string}
+
+        @since version 2.0.0
+        */
+        std::string to_string() const noexcept
+        {
+            return std::accumulate(
+                reference_tokens.begin(), reference_tokens.end(), std::string{},
+                [](const std::string &a, const std::string &b) {
+                    return a + "/" + escape(b);
+                });
+        }
+
+        /// @copydoc to_string()
+        operator std::string() const { return to_string(); }
+
+    private:
+        /// remove and return last reference pointer
+        std::string pop_back()
+        {
+            if (is_root())
+            {
+                JSON_THROW(std::domain_error("JSON pointer has no parent"));
+            }
+
+            auto last = reference_tokens.back();
+            reference_tokens.pop_back();
+            return last;
+        }
+
+        /// return whether pointer points to the root document
+        bool is_root() const { return reference_tokens.empty(); }
+
+        json_pointer top() const
+        {
+            if (is_root())
+            {
+                JSON_THROW(std::domain_error("JSON pointer has no parent"));
+            }
+
+            json_pointer result = *this;
+            result.reference_tokens = {reference_tokens[0]};
+            return result;
+        }
+
+        /*!
+        @brief create and return a reference to the pointed to value
+
+        @complexity Linear in the number of reference tokens.
+        */
+        reference get_and_create(reference j) const
+        {
+            pointer result = &j;
+
+            // in case no reference tokens exist, return a reference to the
+            // JSON value j which will be overwritten by a primitive value
+            for (const auto &reference_token : reference_tokens)
+            {
+                switch (result->m_type)
+                {
+                case value_t::null:
+                {
+                    if (reference_token == "0")
+                    {
+                        // start a new array if reference token is 0
+                        result = &result->operator[](0);
+                    }
+                    else
+                    {
+                        // start a new object otherwise
+                        result = &result->operator[](reference_token);
+                    }
+                    break;
+                }
+
+                case value_t::object:
+                {
+                    // create an entry in the object
+                    result = &result->operator[](reference_token);
+                    break;
+                }
+
+                case value_t::array:
+                {
+                    // create an entry in the array
+                    result = &result->operator[](
+                        static_cast<size_type>(std::stoi(reference_token)));
+                    break;
+                }
+
+                /*
+                The following code is only reached if there exists a
+                reference token _and_ the current value is primitive. In
+                this case, we have an error situation, because primitive
+                values may only occur as single value; that is, with an
+                empty list of reference tokens.
+                */
+                default:
+                {
+                    JSON_THROW(std::domain_error("invalid value to unflatten"));
+                }
+                }
+            }
+
+            return *result;
+        }
+
+        /*!
+        @brief return a reference to the pointed to value
+
+        @note This version does not throw if a value is not present, but tries
+        to create nested values instead. For instance, calling this function
+        with pointer `"/this/that"` on a null value is equivalent to calling
+        `operator[]("this").operator[]("that")` on that value, effectively
+        changing the null value to an object.
+
+        @param[in] ptr  a JSON value
+
+        @return reference to the JSON value pointed to by the JSON pointer
+
+        @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
+        */
+        reference get_unchecked(pointer ptr) const
+        {
+            for (const auto &reference_token : reference_tokens)
+            {
+                // convert null values to arrays or objects before continuing
+                if (ptr->m_type == value_t::null)
+                {
+                    // 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); });
+
+                    // change value to array for numbers or "-" or to object
+                    // otherwise
+                    if (nums or reference_token == "-")
+                    {
+                        *ptr = value_t::array;
+                    }
+                    else
+                    {
+                        *ptr = value_t::object;
+                    }
+                }
+
+                switch (ptr->m_type)
+                {
+                case value_t::object:
+                {
+                    // use unchecked object access
+                    ptr = &ptr->operator[](reference_token);
+                    break;
+                }
+
+                case value_t::array:
+                {
+                    // 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'"));
+                    }
+
+                    if (reference_token == "-")
+                    {
+                        // explicityly 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)));
+                    }
+                    break;
+                }
+
+                default:
+                {
+                    JSON_THROW(
+                        std::out_of_range("unresolved reference token '" +
+                                          reference_token + "'"));
+                }
+                }
+            }
+
+            return *ptr;
+        }
+
+        reference get_checked(pointer ptr) const
+        {
+            for (const auto &reference_token : reference_tokens)
+            {
+                switch (ptr->m_type)
+                {
+                case value_t::object:
+                {
+                    // note: at performs range check
+                    ptr = &ptr->at(reference_token);
+                    break;
+                }
+
+                case value_t::array:
+                {
+                    if (reference_token == "-")
+                    {
+                        // "-" always fails the range check
+                        throw std::out_of_range(
+                            "array index '-' (" +
+                            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'"));
+                    }
+
+                    // note: at performs range check
+                    ptr = &ptr->at(
+                        static_cast<size_type>(std::stoi(reference_token)));
+                    break;
+                }
+
+                default:
+                {
+                    JSON_THROW(
+                        std::out_of_range("unresolved reference token '" +
+                                          reference_token + "'"));
+                }
+                }
+            }
+
+            return *ptr;
+        }
+
+        /*!
+        @brief return a const reference to the pointed to value
+
+        @param[in] ptr  a JSON value
+
+        @return const reference to the JSON value pointed to by the JSON
+                pointer
+        */
+        const_reference get_unchecked(const_pointer ptr) const
+        {
+            for (const auto &reference_token : reference_tokens)
+            {
+                switch (ptr->m_type)
+                {
+                case value_t::object:
+                {
+                    // use unchecked object access
+                    ptr = &ptr->operator[](reference_token);
+                    break;
+                }
+
+                case value_t::array:
+                {
+                    if (reference_token == "-")
+                    {
+                        // "-" cannot be used for const access
+                        throw std::out_of_range(
+                            "array index '-' (" +
+                            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'"));
+                    }
+
+                    // use unchecked array access
+                    ptr = &ptr->operator[](
+                        static_cast<size_type>(std::stoi(reference_token)));
+                    break;
+                }
+
+                default:
+                {
+                    JSON_THROW(
+                        std::out_of_range("unresolved reference token '" +
+                                          reference_token + "'"));
+                }
+                }
+            }
+
+            return *ptr;
+        }
+
+        const_reference get_checked(const_pointer ptr) const
+        {
+            for (const auto &reference_token : reference_tokens)
+            {
+                switch (ptr->m_type)
+                {
+                case value_t::object:
+                {
+                    // note: at performs range check
+                    ptr = &ptr->at(reference_token);
+                    break;
+                }
+
+                case value_t::array:
+                {
+                    if (reference_token == "-")
+                    {
+                        // "-" always fails the range check
+                        throw std::out_of_range(
+                            "array index '-' (" +
+                            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'"));
+                    }
+
+                    // note: at performs range check
+                    ptr = &ptr->at(
+                        static_cast<size_type>(std::stoi(reference_token)));
+                    break;
+                }
+
+                default:
+                {
+                    JSON_THROW(
+                        std::out_of_range("unresolved reference token '" +
+                                          reference_token + "'"));
+                }
+                }
+            }
+
+            return *ptr;
+        }
+
+        /// split the string input to reference tokens
+        static std::vector<std::string>
+        split(const std::string &reference_string)
+        {
+            std::vector<std::string> result;
+
+            // special case: empty reference string -> no reference tokens
+            if (reference_string.empty())
+            {
+                return result;
+            }
+
+            // 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 '/'"));
+            }
+
+            // extract the reference tokens:
+            // - slash: position of the last read slash (or end of string)
+            // - start: position after the previous slash
+            for (
+                // search for the first slash after the first character
+                size_t slash = reference_string.find_first_of('/', 1),
+                       // set the beginning of the first reference token
+                start = 1;
+                // we can stop if start == string::npos+1 = 0
+                start != 0;
+                // set the beginning of the next reference token
+                // (will eventually be 0 if slash == std::string::npos)
+                start = slash + 1,
+                       // find next slash
+                slash = reference_string.find_first_of('/', start))
+            {
+                // use the text between the beginning of the reference token
+                // (start) and the last slash (slash).
+                auto reference_token =
+                    reference_string.substr(start, slash - start);
+
+                // check reference tokens are properly escaped
+                for (size_t pos = reference_token.find_first_of('~');
+                     pos != std::string::npos;
+                     pos = reference_token.find_first_of('~', pos + 1))
+                {
+                    assert(reference_token[pos] == '~');
+
+                    // ~ must be followed by 0 or 1
+                    if (pos == reference_token.size() - 1 or
+                        (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'"));
+                    }
+                }
+
+                // finally, store the reference token
+                unescape(reference_token);
+                result.push_back(reference_token);
+            }
 
-      return result;
-    }
+            return result;
+        }
 
-  private:
-    /*!
-    @brief replace all occurrences of a substring by another string
+    private:
+        /*!
+        @brief replace all occurrences of a substring by another string
 
-    @param[in,out] s  the string to manipulate; changed so that all
-                      occurrences of @a f are replaced with @a t
-    @param[in]     f  the substring to replace with @a t
-    @param[in]     t  the string to replace @a f
+        @param[in,out] s  the string to manipulate; changed so that all
+                          occurrences of @a f are replaced with @a t
+        @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.
 
-    @since version 2.0.0
-    */
-    static void replace_substring(std::string &s, const std::string &f,
-                                  const std::string &t)
-    {
-      assert(not f.empty());
+        @since version 2.0.0
+        */
+        static void replace_substring(std::string &s, const std::string &f,
+                                      const std::string &t)
+        {
+            assert(not f.empty());
 
-      for (size_t pos = s.find(f);         // find first occurrence of f
-           pos != std::string::npos;       // make sure f was found
-           s.replace(pos, f.size(), t),    // replace with t
-           pos = s.find(f, pos + t.size()) // find next occurrence of f
-           )
-        ;
-    }
+            for (size_t pos = s.find(f);         // find first occurrence of f
+                 pos != std::string::npos;       // make sure f was found
+                 s.replace(pos, f.size(), t),    // replace with t
+                 pos = s.find(f, pos + t.size()) // find next occurrence of f
+                 )
+                ;
+        }
 
-    /// escape tilde and slash
-    static std::string escape(std::string s)
-    {
-      // escape "~"" to "~0" and "/" to "~1"
-      replace_substring(s, "~", "~0");
-      replace_substring(s, "/", "~1");
-      return s;
-    }
+        /// escape tilde and slash
+        static std::string escape(std::string s)
+        {
+            // escape "~"" to "~0" and "/" to "~1"
+            replace_substring(s, "~", "~0");
+            replace_substring(s, "/", "~1");
+            return s;
+        }
 
-    /// unescape tilde and slash
-    static void unescape(std::string &s)
-    {
-      // first transform any occurrence of the sequence '~1' to '/'
-      replace_substring(s, "~1", "/");
-      // then transform any occurrence of the sequence '~0' to '~'
-      replace_substring(s, "~0", "~");
-    }
+        /// unescape tilde and slash
+        static void unescape(std::string &s)
+        {
+            // first transform any occurrence of the sequence '~1' to '/'
+            replace_substring(s, "~1", "/");
+            // then transform any occurrence of the sequence '~0' to '~'
+            replace_substring(s, "~0", "~");
+        }
 
-    /*!
-    @param[in] reference_string  the reference string to the current value
-    @param[in] value             the value to consider
-    @param[in,out] result        the result object to insert values to
+        /*!
+        @param[in] reference_string  the reference string to the current value
+        @param[in] value             the value to consider
+        @param[in,out] result        the result object to insert values to
 
-    @note Empty objects or arrays are flattened to `null`.
-    */
-    static void flatten(const std::string &reference_string,
-                        const basic_json &value, basic_json &result)
-    {
-      switch (value.m_type)
-      {
-        case value_t::array:
+        @note Empty objects or arrays are flattened to `null`.
+        */
+        static void flatten(const std::string &reference_string,
+                            const basic_json &value, basic_json &result)
         {
-          if (value.m_value.array->empty())
-          {
-            // flatten empty array as null
-            result[reference_string] = nullptr;
-          }
-          else
-          {
-            // iterate array and use index as reference string
-            for (size_t i = 0; i < value.m_value.array->size(); ++i)
+            switch (value.m_type)
             {
-              flatten(reference_string + "/" + std::to_string(i),
-                      value.m_value.array->operator[](i), result);
+            case value_t::array:
+            {
+                if (value.m_value.array->empty())
+                {
+                    // flatten empty array as null
+                    result[reference_string] = nullptr;
+                }
+                else
+                {
+                    // iterate array and use index as reference string
+                    for (size_t i = 0; i < value.m_value.array->size(); ++i)
+                    {
+                        flatten(reference_string + "/" + std::to_string(i),
+                                value.m_value.array->operator[](i), result);
+                    }
+                }
+                break;
             }
-          }
-          break;
-        }
 
-        case value_t::object:
-        {
-          if (value.m_value.object->empty())
-          {
-            // flatten empty object as null
-            result[reference_string] = nullptr;
-          }
-          else
-          {
-            // iterate object and use keys as reference string
-            for (const auto &element : *value.m_value.object)
+            case value_t::object:
             {
-              flatten(reference_string + "/" + escape(element.first),
-                      element.second, result);
+                if (value.m_value.object->empty())
+                {
+                    // flatten empty object as null
+                    result[reference_string] = nullptr;
+                }
+                else
+                {
+                    // iterate object and use keys as reference string
+                    for (const auto &element : *value.m_value.object)
+                    {
+                        flatten(reference_string + "/" + escape(element.first),
+                                element.second, result);
+                    }
+                }
+                break;
             }
-          }
-          break;
-        }
 
-        default:
-        {
-          // add primitive value with its reference string
-          result[reference_string] = value;
-          break;
+            default:
+            {
+                // add primitive value with its reference string
+                result[reference_string] = value;
+                break;
+            }
+            }
         }
-      }
-    }
-
-    /*!
-    @param[in] value  flattened JSON
-
-    @return unflattened JSON
-    */
-    static basic_json unflatten(const basic_json &value)
-    {
-      if (not value.is_object())
-      {
-        JSON_THROW(std::domain_error("only objects can be unflattened"));
-      }
 
-      basic_json result;
+        /*!
+        @param[in] value  flattened JSON
 
-      // iterate the JSON object values
-      for (const auto &element : *value.m_value.object)
-      {
-        if (not element.second.is_primitive())
+        @return unflattened JSON
+        */
+        static basic_json unflatten(const basic_json &value)
         {
-          JSON_THROW(std::domain_error("values in object must be primitive"));
-        }
+            if (not value.is_object())
+            {
+                JSON_THROW(
+                    std::domain_error("only objects can be unflattened"));
+            }
 
-        // assign value to reference pointed to by JSON pointer; Note
-        // that if the JSON pointer is "" (i.e., points to the whole
-        // value), function get_and_create returns a reference to
-        // result itself. An assignment will then create a primitive
-        // value.
-        json_pointer(element.first).get_and_create(result) = element.second;
-      }
+            basic_json result;
 
-      return result;
-    }
+            // iterate the JSON object values
+            for (const auto &element : *value.m_value.object)
+            {
+                if (not element.second.is_primitive())
+                {
+                    JSON_THROW(std::domain_error(
+                        "values in object must be primitive"));
+                }
+
+                // assign value to reference pointed to by JSON pointer; Note
+                // that if the JSON pointer is "" (i.e., points to the whole
+                // value), function get_and_create returns a reference to
+                // result itself. An assignment will then create a primitive
+                // value.
+                json_pointer(element.first).get_and_create(result) =
+                    element.second;
+            }
 
-  private:
-    /// the reference tokens
-    std::vector<std::string> reference_tokens{};
-  };
+            return result;
+        }
 
-  //////////////////////////
-  // JSON Pointer support //
-  //////////////////////////
+    private:
+        /// the reference tokens
+        std::vector<std::string> reference_tokens{};
+    };
 
-  /// @name JSON Pointer functions
-  /// @{
+    //////////////////////////
+    // JSON Pointer support //
+    //////////////////////////
 
-  /*!
-  @brief access specified element via JSON Pointer
+    /// @name JSON Pointer functions
+    /// @{
 
-  Uses a JSON pointer to retrieve a reference to the respective JSON value.
-  No bound checking is performed. Similar to @ref operator[](const typename
-  object_t::key_type&), `null` values are created in arrays and objects if
-  necessary.
+    /*!
+    @brief access specified element via JSON Pointer
 
-  In particular:
-  - If the JSON pointer points to an object key that does not exist, it
-    is created an filled with a `null` value before a reference to it
-    is returned.
-  - If the JSON pointer points to an array index that does not exist, it
-    is created an filled with a `null` value before a reference to it
-    is returned. All indices between the current maximum and the given
-    index are also filled with `null`.
-  - The special value `-` is treated as a synonym for the index past the
-    end.
+    Uses a JSON pointer to retrieve a reference to the respective JSON value.
+    No bound checking is performed. Similar to @ref operator[](const typename
+    object_t::key_type&), `null` values are created in arrays and objects if
+    necessary.
 
-  @param[in] ptr  a JSON pointer
+    In particular:
+    - If the JSON pointer points to an object key that does not exist, it
+      is created an filled with a `null` value before a reference to it
+      is returned.
+    - If the JSON pointer points to an array index that does not exist, it
+      is created an filled with a `null` value before a reference to it
+      is returned. All indices between the current maximum and the given
+      index are also filled with `null`.
+    - The special value `-` is treated as a synonym for the index past the
+      end.
 
-  @return reference to the element pointed to by @a ptr
+    @param[in] ptr  a JSON pointer
 
-  @complexity Constant.
+    @return reference to the element pointed to by @a ptr
 
-  @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
+    @complexity Constant.
 
-  @liveexample{The behavior is shown in the example.,operatorjson_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
 
-  @since version 2.0.0
-  */
-  reference operator[](const json_pointer &ptr)
-  {
-    return ptr.get_unchecked(this);
-  }
+    @liveexample{The behavior is shown in the example.,operatorjson_pointer}
 
-  /*!
-  @brief access specified element via JSON Pointer
+    @since version 2.0.0
+    */
+    reference operator[](const json_pointer &ptr)
+    {
+        return ptr.get_unchecked(this);
+    }
 
-  Uses a JSON pointer to retrieve a reference to the respective JSON value.
-  No bound checking is performed. The function does not change the JSON
-  value; no `null` values are created. In particular, the the special value
-  `-` yields an exception.
+    /*!
+    @brief access specified element via JSON Pointer
 
-  @param[in] ptr  JSON pointer to the desired element
+    Uses a JSON pointer to retrieve a reference to the respective JSON value.
+    No bound checking is performed. The function does not change the JSON
+    value; no `null` values are created. In particular, the the special value
+    `-` yields an exception.
 
-  @return const reference to the element pointed to by @a ptr
+    @param[in] ptr  JSON pointer to the desired element
 
-  @complexity Constant.
+    @return const reference to the element pointed to by @a ptr
 
-  @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
+    @complexity Constant.
 
-  @liveexample{The behavior is shown in the example.,operatorjson_pointer_const}
+    @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
 
-  @since version 2.0.0
-  */
-  const_reference operator[](const json_pointer &ptr) const
-  {
-    return ptr.get_unchecked(this);
-  }
+    @liveexample{The behavior is shown in the
+    example.,operatorjson_pointer_const}
 
-  /*!
-  @brief access specified element via JSON Pointer
+    @since version 2.0.0
+    */
+    const_reference operator[](const json_pointer &ptr) const
+    {
+        return ptr.get_unchecked(this);
+    }
 
-  Returns a reference to the element at with specified JSON pointer @a ptr,
-  with bounds checking.
+    /*!
+    @brief access specified element via JSON Pointer
 
-  @param[in] ptr  JSON pointer to the desired element
+    Returns a reference to the element at with specified JSON pointer @a ptr,
+    with bounds checking.
 
-  @return reference to the element pointed to by @a ptr
+    @param[in] ptr  JSON pointer to the desired element
 
-  @complexity Constant.
+    @return reference to the element pointed to by @a ptr
 
-  @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
+    @complexity Constant.
 
-  @liveexample{The behavior is shown in the example.,at_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
 
-  @since version 2.0.0
-  */
-  reference at(const json_pointer &ptr) { return ptr.get_checked(this); }
+    @liveexample{The behavior is shown in the example.,at_json_pointer}
 
-  /*!
-  @brief access specified element via JSON Pointer
+    @since version 2.0.0
+    */
+    reference at(const json_pointer &ptr) { return ptr.get_checked(this); }
 
-  Returns a const reference to the element at with specified JSON pointer @a
-  ptr, with bounds checking.
+    /*!
+    @brief access specified element via JSON Pointer
 
-  @param[in] ptr  JSON pointer to the desired element
+    Returns a const reference to the element at with specified JSON pointer @a
+    ptr, with bounds checking.
 
-  @return reference to the element pointed to by @a ptr
+    @param[in] ptr  JSON pointer to the desired element
 
-  @complexity Constant.
+    @return reference to the element pointed to by @a ptr
 
-  @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
+    @complexity Constant.
 
-  @liveexample{The behavior is shown in the example.,at_json_pointer_const}
+    @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
 
-  @since version 2.0.0
-  */
-  const_reference at(const json_pointer &ptr) const
-  {
-    return ptr.get_checked(this);
-  }
+    @liveexample{The behavior is shown in the example.,at_json_pointer_const}
 
-  /*!
-  @brief return flattened JSON value
+    @since version 2.0.0
+    */
+    const_reference at(const json_pointer &ptr) const
+    {
+        return ptr.get_checked(this);
+    }
 
-  The function creates a JSON object whose keys are JSON pointers (see [RFC
-  6901](https://tools.ietf.org/html/rfc6901)) and whose values are all
-  primitive. The original JSON value can be restored using the @ref
-  unflatten() function.
+    /*!
+    @brief return flattened JSON value
 
-  @return an object that maps JSON pointers to primitve values
+    The function creates a JSON object whose keys are JSON pointers (see [RFC
+    6901](https://tools.ietf.org/html/rfc6901)) and whose values are all
+    primitive. The original JSON value can be restored using the @ref
+    unflatten() function.
 
-  @note Empty objects and arrays are flattened to `null` and will not be
-        reconstructed correctly by the @ref unflatten() function.
+    @return an object that maps JSON pointers to primitve values
 
-  @complexity Linear in the size the JSON value.
+    @note Empty objects and arrays are flattened to `null` and will not be
+          reconstructed correctly by the @ref unflatten() function.
 
-  @liveexample{The following code shows how a JSON object is flattened to an
-  object whose keys consist of JSON pointers.,flatten}
+    @complexity Linear in the size the JSON value.
 
-  @sa @ref unflatten() for the reverse function
+    @liveexample{The following code shows how a JSON object is flattened to an
+    object whose keys consist of JSON pointers.,flatten}
 
-  @since version 2.0.0
-  */
-  basic_json flatten() const
-  {
-    basic_json result(value_t::object);
-    json_pointer::flatten("", *this, result);
-    return result;
-  }
+    @sa @ref unflatten() for the reverse function
 
-  /*!
-  @brief unflatten a previously flattened JSON value
+    @since version 2.0.0
+    */
+    basic_json flatten() const
+    {
+        basic_json result(value_t::object);
+        json_pointer::flatten("", *this, result);
+        return result;
+    }
 
-  The function restores the arbitrary nesting of a JSON value that has been
-  flattened before using the @ref flatten() function. The JSON value must
-  meet certain constraints:
-  1. The value must be an object.
-  2. The keys must be JSON pointers (see
-     [RFC 6901](https://tools.ietf.org/html/rfc6901))
-  3. The mapped values must be primitive JSON types.
+    /*!
+    @brief unflatten a previously flattened JSON value
 
-  @return the original JSON from a flattened version
+    The function restores the arbitrary nesting of a JSON value that has been
+    flattened before using the @ref flatten() function. The JSON value must
+    meet certain constraints:
+    1. The value must be an object.
+    2. The keys must be JSON pointers (see
+       [RFC 6901](https://tools.ietf.org/html/rfc6901))
+    3. The mapped values must be primitive JSON types.
 
-  @note Empty objects and arrays are flattened by @ref flatten() to `null`
-        values and can not unflattened to their original type. Apart from
-        this example, for a JSON value `j`, the following is always true:
-        `j == j.flatten().unflatten()`.
+    @return the original JSON from a flattened version
 
-  @complexity Linear in the size the JSON value.
+    @note Empty objects and arrays are flattened by @ref flatten() to `null`
+          values and can not unflattened to their original type. Apart from
+          this example, for a JSON value `j`, the following is always true:
+          `j == j.flatten().unflatten()`.
 
-  @liveexample{The following code shows how a flattened JSON object is
-  unflattened into the original nested JSON object.,unflatten}
+    @complexity Linear in the size the JSON value.
 
-  @sa @ref flatten() for the reverse function
+    @liveexample{The following code shows how a flattened JSON object is
+    unflattened into the original nested JSON object.,unflatten}
 
-  @since version 2.0.0
-  */
-  basic_json unflatten() const { return json_pointer::unflatten(*this); }
+    @sa @ref flatten() for the reverse function
 
-  /// @}
+    @since version 2.0.0
+    */
+    basic_json unflatten() const { return json_pointer::unflatten(*this); }
 
-  //////////////////////////
-  // JSON Patch functions //
-  //////////////////////////
+    /// @}
 
-  /// @name JSON Patch functions
-  /// @{
+    //////////////////////////
+    // JSON Patch functions //
+    //////////////////////////
 
-  /*!
-  @brief applies a JSON patch
+    /// @name JSON Patch functions
+    /// @{
 
-  [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
-  executing all operations from the patch.
+    /*!
+    @brief applies a JSON patch
 
-  @param[in] json_patch  JSON patch document
-  @return patched document
+    [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
+    executing all operations from the patch.
 
-  @note The application of a patch is atomic: Either all operations succeed
-        and the patched document is returned or an exception is thrown. In
-        any case, the original value is not changed: the patch is applied
-        to a copy of the value.
+    @param[in] json_patch  JSON patch document
+    @return patched document
 
-  @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
-  attributes are missing); example: `"operation add must have member path"`
+    @note The application of a patch is atomic: Either all operations succeed
+          and the patched document is returned or an exception is thrown. In
+          any case, the original value is not changed: the patch is applied
+          to a copy of the value.
 
-  @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.
+    @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
+    attributes are missing); example: `"operation add must have member path"`
 
-  @liveexample{The following code shows how a JSON patch is applied to a
-  value.,patch}
+    @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.
 
-  @sa @ref diff -- create a JSON patch by comparing two JSON values
+    @liveexample{The following code shows how a JSON patch is applied to a
+    value.,patch}
 
-  @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902)
-  @sa [RFC 6901 (JSON Pointer)](https://tools.ietf.org/html/rfc6901)
+    @sa @ref diff -- create a JSON patch by comparing two JSON values
 
-  @since version 2.0.0
-  */
-  basic_json patch(const basic_json &json_patch) const
-  {
-    // make a working copy to apply the patch to
-    basic_json result = *this;
+    @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902)
+    @sa [RFC 6901 (JSON Pointer)](https://tools.ietf.org/html/rfc6901)
 
-    // the valid JSON Patch operations
-    enum class patch_operations
+    @since version 2.0.0
+    */
+    basic_json patch(const basic_json &json_patch) const
     {
-      add,
-      remove,
-      replace,
-      move,
-      copy,
-      test,
-      invalid
-    };
+        // make a working copy to apply the patch to
+        basic_json result = *this;
 
-    const auto get_op = [](const std::string op) {
-      if (op == "add")
-      {
-        return patch_operations::add;
-      }
-      if (op == "remove")
-      {
-        return patch_operations::remove;
-      }
-      if (op == "replace")
-      {
-        return patch_operations::replace;
-      }
-      if (op == "move")
-      {
-        return patch_operations::move;
-      }
-      if (op == "copy")
-      {
-        return patch_operations::copy;
-      }
-      if (op == "test")
-      {
-        return patch_operations::test;
-      }
-
-      return patch_operations::invalid;
-    };
+        // the valid JSON Patch operations
+        enum class patch_operations
+        {
+            add,
+            remove,
+            replace,
+            move,
+            copy,
+            test,
+            invalid
+        };
 
-    // wrapper for "add" operation; add value at ptr
-    const auto operation_add = [&result](json_pointer &ptr, basic_json val) {
-      // adding to the root of the target document means replacing it
-      if (ptr.is_root())
-      {
-        result = val;
-      }
-      else
-      {
-        // make sure the top element of the pointer exists
-        json_pointer top_pointer = ptr.top();
-        if (top_pointer != ptr)
-        {
-          result.at(top_pointer);
-        }
-
-        // get reference to parent of JSON pointer ptr
-        const auto last_path = ptr.pop_back();
-        basic_json &parent = result[ptr];
-
-        switch (parent.m_type)
-        {
-          case value_t::null:
-          case value_t::object:
-          {
-            // use operator[] to add value
-            parent[last_path] = val;
-            break;
-          }
+        const auto get_op = [](const std::string op) {
+            if (op == "add")
+            {
+                return patch_operations::add;
+            }
+            if (op == "remove")
+            {
+                return patch_operations::remove;
+            }
+            if (op == "replace")
+            {
+                return patch_operations::replace;
+            }
+            if (op == "move")
+            {
+                return patch_operations::move;
+            }
+            if (op == "copy")
+            {
+                return patch_operations::copy;
+            }
+            if (op == "test")
+            {
+                return patch_operations::test;
+            }
+
+            return patch_operations::invalid;
+        };
 
-          case value_t::array:
-          {
-            if (last_path == "-")
+        // wrapper for "add" operation; add value at ptr
+        const auto operation_add = [&result](json_pointer &ptr,
+                                             basic_json val) {
+            // adding to the root of the target document means replacing it
+            if (ptr.is_root())
             {
-              // special case: append to back
-              parent.push_back(val);
+                result = val;
             }
             else
             {
-              const auto idx = std::stoi(last_path);
-              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"));
-              }
-              else
-              {
-                // default case: insert add offset
-                parent.insert(
-                    parent.begin() + static_cast<difference_type>(idx), val);
-              }
-            }
-            break;
-          }
+                // make sure the top element of the pointer exists
+                json_pointer top_pointer = ptr.top();
+                if (top_pointer != ptr)
+                {
+                    result.at(top_pointer);
+                }
 
-          default:
-          {
-            // if there exists a parent it cannot be primitive
-            assert(false); // LCOV_EXCL_LINE
-          }
-        }
-      }
-    };
+                // get reference to parent of JSON pointer ptr
+                const auto last_path = ptr.pop_back();
+                basic_json &parent = result[ptr];
 
-    // wrapper for "remove" operation; remove value at ptr
-    const auto operation_remove = [&result](json_pointer &ptr) {
-      // get reference to parent of JSON pointer ptr
-      const auto last_path = ptr.pop_back();
-      basic_json &parent = result.at(ptr);
+                switch (parent.m_type)
+                {
+                case value_t::null:
+                case value_t::object:
+                {
+                    // use operator[] to add value
+                    parent[last_path] = val;
+                    break;
+                }
 
-      // remove child
-      if (parent.is_object())
-      {
-        // perform range check
-        auto it = parent.find(last_path);
-        if (it != parent.end())
-        {
-          parent.erase(it);
-        }
-        else
-        {
-          JSON_THROW(std::out_of_range("key '" + last_path + "' not found"));
-        }
-      }
-      else if (parent.is_array())
-      {
-        // note erase performs range check
-        parent.erase(static_cast<size_type>(std::stoi(last_path)));
-      }
-    };
+                case value_t::array:
+                {
+                    if (last_path == "-")
+                    {
+                        // special case: append to back
+                        parent.push_back(val);
+                    }
+                    else
+                    {
+                        const auto idx = std::stoi(last_path);
+                        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"));
+                        }
+                        else
+                        {
+                            // default case: insert add offset
+                            parent.insert(parent.begin() +
+                                              static_cast<difference_type>(idx),
+                                          val);
+                        }
+                    }
+                    break;
+                }
 
-    // type check
-    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"));
-    }
+                default:
+                {
+                    // if there exists a parent it cannot be primitive
+                    assert(false); // LCOV_EXCL_LINE
+                }
+                }
+            }
+        };
 
-    // iterate and apply th eoperations
-    for (const auto &val : json_patch)
-    {
-      // wrapper to get a value for an operation
-      const auto get_value = [&val](const std::string &op,
-                                    const std::string &member,
-                                    bool string_type) -> basic_json & {
-        // find value
-        auto it = val.m_value.object->find(member);
+        // wrapper for "remove" operation; remove value at ptr
+        const auto operation_remove = [&result](json_pointer &ptr) {
+            // get reference to parent of JSON pointer ptr
+            const auto last_path = ptr.pop_back();
+            basic_json &parent = result.at(ptr);
 
-        // context-sensitive error message
-        const auto error_msg =
-            (op == "op") ? "operation" : "operation '" + op + "'";
+            // remove child
+            if (parent.is_object())
+            {
+                // perform range check
+                auto it = parent.find(last_path);
+                if (it != parent.end())
+                {
+                    parent.erase(it);
+                }
+                else
+                {
+                    JSON_THROW(
+                        std::out_of_range("key '" + last_path + "' not found"));
+                }
+            }
+            else if (parent.is_array())
+            {
+                // note erase performs range check
+                parent.erase(static_cast<size_type>(std::stoi(last_path)));
+            }
+        };
 
-        // check if desired value is present
-        if (it == val.m_value.object->end())
+        // type check
+        if (not json_patch.is_array())
         {
-          JSON_THROW(std::invalid_argument(error_msg + " must have member '" +
-                                           member + "'"));
+            // a JSON patch must be an array of objects
+            JSON_THROW(std::invalid_argument(
+                "JSON patch must be an array of objects"));
         }
 
-        // check if result is of type string
-        if (string_type and not it->second.is_string())
+        // iterate and apply th eoperations
+        for (const auto &val : json_patch)
         {
-          JSON_THROW(std::invalid_argument(
-              error_msg + " must have string member '" + member + "'"));
-        }
+            // wrapper to get a value for an operation
+            const auto get_value = [&val](const std::string &op,
+                                          const std::string &member,
+                                          bool string_type) -> basic_json & {
+                // find value
+                auto it = val.m_value.object->find(member);
 
-        // no error: return value
-        return it->second;
-      };
+                // context-sensitive error message
+                const auto error_msg =
+                    (op == "op") ? "operation" : "operation '" + op + "'";
 
-      // type check
-      if (not val.is_object())
-      {
-        JSON_THROW(
-            std::invalid_argument("JSON patch must be an array of objects"));
-      }
+                // check if desired value is present
+                if (it == val.m_value.object->end())
+                {
+                    JSON_THROW(std::invalid_argument(
+                        error_msg + " must have member '" + member + "'"));
+                }
 
-      // collect mandatory members
-      const std::string op = get_value("op", "op", true);
-      const std::string path = get_value(op, "path", true);
-      json_pointer ptr(path);
+                // 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 +
+                        "'"));
+                }
 
-      switch (get_op(op))
-      {
-        case patch_operations::add:
-        {
-          operation_add(ptr, get_value("add", "value", false));
-          break;
-        }
+                // no error: return value
+                return it->second;
+            };
 
-        case patch_operations::remove:
-        {
-          operation_remove(ptr);
-          break;
-        }
+            // type check
+            if (not val.is_object())
+            {
+                JSON_THROW(std::invalid_argument(
+                    "JSON patch must be an array of objects"));
+            }
 
-        case patch_operations::replace:
-        {
-          // the "path" location must exist - use at()
-          result.at(ptr) = get_value("replace", "value", false);
-          break;
-        }
+            // collect mandatory members
+            const std::string op = get_value("op", "op", true);
+            const std::string path = get_value(op, "path", true);
+            json_pointer ptr(path);
 
-        case patch_operations::move:
-        {
-          const std::string from_path = get_value("move", "from", true);
-          json_pointer from_ptr(from_path);
+            switch (get_op(op))
+            {
+            case patch_operations::add:
+            {
+                operation_add(ptr, get_value("add", "value", false));
+                break;
+            }
 
-          // the "from" location must exist - use at()
-          basic_json v = result.at(from_ptr);
+            case patch_operations::remove:
+            {
+                operation_remove(ptr);
+                break;
+            }
 
-          // The move operation is functionally identical to a
-          // "remove" operation on the "from" location, followed
-          // immediately by an "add" operation at the target
-          // location with the value that was just removed.
-          operation_remove(from_ptr);
-          operation_add(ptr, v);
-          break;
-        }
+            case patch_operations::replace:
+            {
+                // the "path" location must exist - use at()
+                result.at(ptr) = get_value("replace", "value", false);
+                break;
+            }
 
-        case patch_operations::copy:
-        {
-          const std::string from_path = get_value("copy", "from", true);
-          ;
-          const json_pointer from_ptr(from_path);
+            case patch_operations::move:
+            {
+                const std::string from_path = get_value("move", "from", true);
+                json_pointer from_ptr(from_path);
+
+                // the "from" location must exist - use at()
+                basic_json v = result.at(from_ptr);
+
+                // The move operation is functionally identical to a
+                // "remove" operation on the "from" location, followed
+                // immediately by an "add" operation at the target
+                // location with the value that was just removed.
+                operation_remove(from_ptr);
+                operation_add(ptr, v);
+                break;
+            }
 
-          // the "from" location must exist - use at()
-          result[ptr] = result.at(from_ptr);
-          break;
-        }
+            case patch_operations::copy:
+            {
+                const std::string from_path = get_value("copy", "from", true);
+                ;
+                const json_pointer from_ptr(from_path);
 
-        case patch_operations::test:
-        {
-          bool success = false;
-          JSON_TRY
-          {
-            // check if "value" matches the one at "path"
-            // the "path" location must exist - use at()
-            success = (result.at(ptr) == get_value("test", "value", false));
-          }
-          JSON_CATCH(std::out_of_range &)
-          {
-            // ignore out of range errors: success remains false
-          }
+                // the "from" location must exist - use at()
+                result[ptr] = result.at(from_ptr);
+                break;
+            }
+
+            case patch_operations::test:
+            {
+                bool success = false;
+                JSON_TRY
+                {
+                    // check if "value" matches the one at "path"
+                    // the "path" location must exist - use at()
+                    success =
+                        (result.at(ptr) == get_value("test", "value", false));
+                }
+                JSON_CATCH(std::out_of_range &)
+                {
+                    // ignore out of range errors: success remains false
+                }
 
-          // throw an exception if test fails
-          if (not success)
-          {
-            JSON_THROW(std::domain_error("unsuccessful: " + val.dump()));
-          }
+                // throw an exception if test fails
+                if (not success)
+                {
+                    JSON_THROW(
+                        std::domain_error("unsuccessful: " + val.dump()));
+                }
 
-          break;
-        }
+                break;
+            }
 
-        case patch_operations::invalid:
-        {
-          // op must be "add", "remove", "replace", "move", "copy", or
-          // "test"
-          JSON_THROW(
-              std::invalid_argument("operation value '" + op + "' is invalid"));
+            case patch_operations::invalid:
+            {
+                // op must be "add", "remove", "replace", "move", "copy", or
+                // "test"
+                JSON_THROW(std::invalid_argument("operation value '" + op +
+                                                 "' is invalid"));
+            }
+            }
         }
-      }
-    }
-
-    return result;
-  }
 
-  /*!
-  @brief creates a diff as a JSON patch
+        return result;
+    }
 
-  Creates a [JSON Patch](http://jsonpatch.com) so that value @a source can
-  be changed into the value @a target by calling @ref patch function.
+    /*!
+    @brief creates a diff as a JSON patch
 
-  @invariant For two JSON values @a source and @a target, the following code
-  yields always `true`:
-  @code {.cpp}
-  source.patch(diff(source, target)) == target;
-  @endcode
+    Creates a [JSON Patch](http://jsonpatch.com) so that value @a source can
+    be changed into the value @a target by calling @ref patch function.
 
-  @note Currently, only `remove`, `add`, and `replace` operations are
-        generated.
+    @invariant For two JSON values @a source and @a target, the following code
+    yields always `true`:
+    @code {.cpp}
+    source.patch(diff(source, target)) == target;
+    @endcode
 
-  @param[in] source  JSON value to copare from
-  @param[in] target  JSON value to copare against
-  @param[in] path    helper value to create JSON pointers
+    @note Currently, only `remove`, `add`, and `replace` operations are
+          generated.
 
-  @return a JSON patch to convert the @a source to @a target
+    @param[in] source  JSON value to copare from
+    @param[in] target  JSON value to copare against
+    @param[in] path    helper value to create JSON pointers
 
-  @complexity Linear in the lengths of @a source and @a target.
+    @return a JSON patch to convert the @a source to @a target
 
-  @liveexample{The following code shows how a JSON patch is created as a
-  diff for two JSON values.,diff}
+    @complexity Linear in the lengths of @a source and @a target.
 
-  @sa @ref patch -- apply a JSON patch
+    @liveexample{The following code shows how a JSON patch is created as a
+    diff for two JSON values.,diff}
 
-  @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902)
+    @sa @ref patch -- apply a JSON patch
 
-  @since version 2.0.0
-  */
-  static basic_json diff(const basic_json &source, const basic_json &target,
-                         const std::string &path = "")
-  {
-    // the patch
-    basic_json result(value_t::array);
+    @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902)
 
-    // if the values are the same, return empty patch
-    if (source == target)
+    @since version 2.0.0
+    */
+    static basic_json diff(const basic_json &source, const basic_json &target,
+                           const std::string &path = "")
     {
-      return result;
-    }
+        // the patch
+        basic_json result(value_t::array);
 
-    if (source.type() != target.type())
-    {
-      // different types: replace value
-      result.push_back({{"op", "replace"}, {"path", path}, {"value", target}});
-    }
-    else
-    {
-      switch (source.type())
-      {
-        case value_t::array:
+        // if the values are the same, return empty patch
+        if (source == target)
         {
-          // first pass: traverse common elements
-          size_t i = 0;
-          while (i < source.size() and i < target.size())
-          {
-            // recursive call to compare array values at index i
-            auto temp_diff =
-                diff(source[i], target[i], path + "/" + std::to_string(i));
-            result.insert(result.end(), temp_diff.begin(), temp_diff.end());
-            ++i;
-          }
-
-          // i now reached the end of at least one array
-          // in a second pass, traverse the remaining elements
-
-          // remove my remaining elements
-          const auto end_index = static_cast<difference_type>(result.size());
-          while (i < source.size())
-          {
-            // add operations in reverse order to avoid invalid
-            // indices
-            result.insert(result.begin() + end_index,
-                          object({{"op", "remove"},
-                                  {"path", path + "/" + std::to_string(i)}}));
-            ++i;
-          }
-
-          // add other remaining elements
-          while (i < target.size())
-          {
-            result.push_back({{"op", "add"},
-                              {"path", path + "/" + std::to_string(i)},
-                              {"value", target[i]}});
-            ++i;
-          }
-
-          break;
+            return result;
         }
 
-        case value_t::object:
+        if (source.type() != target.type())
         {
-          // first pass: traverse this object's elements
-          for (auto it = source.begin(); it != source.end(); ++it)
-          {
-            // escape the key name to be used in a JSON patch
-            const auto key = json_pointer::escape(it.key());
-
-            if (target.find(it.key()) != target.end())
+            // different types: replace value
+            result.push_back(
+                {{"op", "replace"}, {"path", path}, {"value", target}});
+        }
+        else
+        {
+            switch (source.type())
             {
-              // recursive call to compare object values at key it
-              auto temp_diff =
-                  diff(it.value(), target[it.key()], path + "/" + key);
-              result.insert(result.end(), temp_diff.begin(), temp_diff.end());
-            }
-            else
+            case value_t::array:
             {
-              // found a key that is not in o -> remove it
-              result.push_back(
-                  object({{"op", "remove"}, {"path", path + "/" + key}}));
+                // first pass: traverse common elements
+                size_t i = 0;
+                while (i < source.size() and i < target.size())
+                {
+                    // recursive call to compare array values at index i
+                    auto temp_diff = diff(source[i], target[i],
+                                          path + "/" + std::to_string(i));
+                    result.insert(result.end(), temp_diff.begin(),
+                                  temp_diff.end());
+                    ++i;
+                }
+
+                // i now reached the end of at least one array
+                // in a second pass, traverse the remaining elements
+
+                // remove my remaining elements
+                const auto end_index =
+                    static_cast<difference_type>(result.size());
+                while (i < source.size())
+                {
+                    // add operations in reverse order to avoid invalid
+                    // indices
+                    result.insert(
+                        result.begin() + end_index,
+                        object({{"op", "remove"},
+                                {"path", path + "/" + std::to_string(i)}}));
+                    ++i;
+                }
+
+                // add other remaining elements
+                while (i < target.size())
+                {
+                    result.push_back({{"op", "add"},
+                                      {"path", path + "/" + std::to_string(i)},
+                                      {"value", target[i]}});
+                    ++i;
+                }
+
+                break;
             }
-          }
 
-          // second pass: traverse other object's elements
-          for (auto it = target.begin(); it != target.end(); ++it)
-          {
-            if (source.find(it.key()) == source.end())
+            case value_t::object:
             {
-              // found a key that is not in this -> add it
-              const auto key = json_pointer::escape(it.key());
-              result.push_back({{"op", "add"},
-                                {"path", path + "/" + key},
-                                {"value", it.value()}});
+                // first pass: traverse this object's elements
+                for (auto it = source.begin(); it != source.end(); ++it)
+                {
+                    // escape the key name to be used in a JSON patch
+                    const auto key = json_pointer::escape(it.key());
+
+                    if (target.find(it.key()) != target.end())
+                    {
+                        // recursive call to compare object values at key it
+                        auto temp_diff = diff(it.value(), target[it.key()],
+                                              path + "/" + key);
+                        result.insert(result.end(), temp_diff.begin(),
+                                      temp_diff.end());
+                    }
+                    else
+                    {
+                        // found a key that is not in o -> remove it
+                        result.push_back(object(
+                            {{"op", "remove"}, {"path", path + "/" + key}}));
+                    }
+                }
+
+                // second pass: traverse other object's elements
+                for (auto it = target.begin(); it != target.end(); ++it)
+                {
+                    if (source.find(it.key()) == source.end())
+                    {
+                        // found a key that is not in this -> add it
+                        const auto key = json_pointer::escape(it.key());
+                        result.push_back({{"op", "add"},
+                                          {"path", path + "/" + key},
+                                          {"value", it.value()}});
+                    }
+                }
+
+                break;
             }
-          }
 
-          break;
+            default:
+            {
+                // both primitive type: replace value
+                result.push_back(
+                    {{"op", "replace"}, {"path", path}, {"value", target}});
+                break;
+            }
+            }
         }
 
-        default:
-        {
-          // both primitive type: replace value
-          result.push_back(
-              {{"op", "replace"}, {"path", path}, {"value", target}});
-          break;
-        }
-      }
+        return result;
     }
 
-    return result;
-  }
-
-  /// @}
+    /// @}
 };
 
 /////////////
@@ -12327,23 +12480,24 @@ inline void swap(nlohmann::json &j1, nlohmann::json &j2) noexcept(
     is_nothrow_move_constructible<nlohmann::json>::value
         and is_nothrow_move_assignable<nlohmann::json>::value)
 {
-  j1.swap(j2);
+    j1.swap(j2);
 }
 
 /// hash value for JSON objects
-template <> struct hash<nlohmann::json>
+template <>
+struct hash<nlohmann::json>
 {
-  /*!
-  @brief return a hash value for a JSON object
-
-  @since version 1.0.0
-  */
-  std::size_t operator()(const nlohmann::json &j) const
-  {
-    // a naive hashing via the string representation
-    const auto &h = hash<nlohmann::json::string_t>();
-    return h(j.dump());
-  }
+    /*!
+    @brief return a hash value for a JSON object
+
+    @since version 1.0.0
+    */
+    std::size_t operator()(const nlohmann::json &j) const
+    {
+        // a naive hashing via the string representation
+        const auto &h = hash<nlohmann::json::string_t>();
+        return h(j.dump());
+    }
 };
 } // namespace std
 
@@ -12362,7 +12516,7 @@ if no parse error occurred.
 */
 inline nlohmann::json operator"" _json(const char *s, std::size_t n)
 {
-  return nlohmann::json::parse(s, s + n);
+    return nlohmann::json::parse(s, s + n);
 }
 
 /*!
@@ -12382,7 +12536,7 @@ object if no parse error occurred.
 inline nlohmann::json::json_pointer operator"" _json_pointer(const char *s,
                                                              std::size_t n)
 {
-  return nlohmann::json::json_pointer(std::string(s, n));
+    return nlohmann::json::json_pointer(std::string(s, n));
 }
 
 // restore GCC/clang diagnostic settings
-- 
GitLab