diff --git a/README.md b/README.md
index 69676370..e2e36fd1 100644
--- a/README.md
+++ b/README.md
@@ -21,6 +21,7 @@
   - [Conversion from STL containers](#conversion-from-stl-containers)
   - [JSON Pointer and JSON Patch](#json-pointer-and-json-patch)
   - [Implicit conversions](#implicit-conversions)
+  - [Conversions to/from arbitrary types](#arbitrary-types-conversions)
   - [Binary formats (CBOR and MessagePack)](#binary-formats-cbor-and-messagepack)
 - [Supported compilers](#supported-compilers)
 - [License](#license)
@@ -442,6 +443,224 @@ int vi = jn.get<int>();
 // etc.
 ```
 
+### Arbitrary types conversions
+
+Every type can be serialized in JSON, not just STL-containers and scalar types. Usually, you would do something along those lines:
+
+```cpp
+namespace ns {
+    // a simple struct to model a person
+    struct person {
+        std::string name;
+        std::string address;
+        int age;
+    };
+}
+
+ns::person p = {"Ned Flanders", "744 Evergreen Terrace", 60};
+
+// convert to JSON: copy each value into the JSON object
+json j;
+j["name"] = p.name;
+j["address"] = p.address;
+j["age"] = p.age;
+
+// ...
+
+// convert from JSON: copy each value from the JSON object
+ns::person p {
+    j["name"].get<std::string>(),
+    j["address"].get<std::string>(),
+    j["age"].get<int>()
+};
+```
+
+It works, but that's quite a lot of boilerplate... Fortunately, there's a better way:
+
+```cpp
+// create a person
+ns::person p {"Ned Flanders", "744 Evergreen Terrace", 60};
+
+// conversion: person -> json
+json j = p;
+
+std::cout << j << std::endl;
+// {"address":"744 Evergreen Terrace","age":60,"name":"Ned Flanders"}
+
+// conversion: json -> person
+ns::person p2 = j;
+
+// that's it
+assert(p == p2);
+```
+
+#### Basic usage
+
+To make this work with one of your types, you only need to provide two functions:
+
+```cpp
+using nlohmann::json;
+
+namespace ns {
+    void to_json(json& j, const person& p) {
+        j = json{{"name", p.name}, {"address", p.address}, {"age", p.age}};
+    }
+
+    void from_json(const json& j, person& p) {
+        p.name = j["name"].get<std::string>();
+        p.address = j["address"].get<std::string>();
+        p.age = j["age"].get<int>();
+    }
+} // namespace ns
+```
+
+That's all! When calling the `json` constructor with your type, your custom `to_json` method will be automatically called.
+Likewise, when calling `get<your_type>()`, the `from_json` method will be called.
+
+Some important things:
+
+* Those methods **MUST** be in your type's namespace (which can be the global namespace), or the library will not be able to locate them (in this example, they are in namespace `ns`, where `person` is defined).
+* When using `get<your_type>()`, `your_type` **MUST** be [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible). (There is a way to bypass this requirement described later.)
+
+#### How do I convert third-party types?
+
+This requires a bit more advanced technique. But first, let's see how this conversion mechanism works:
+
+The library uses **JSON Serializers** to convert types to json.
+The default serializer for `nlohmann::json` is `nlohmann::adl_serializer` (ADL means [Argument-Dependent Lookup](http://en.cppreference.com/w/cpp/language/adl)).
+
+It is implemented like this (simplified):
+
+```cpp
+template <typename T>
+struct adl_serializer {
+    static void to_json(json& j, const T& value) {
+        // calls the "to_json" method in T's namespace
+    }
+
+    static void from_json(const json& j, T& value) {
+        // same thing, but with the "from_json" method
+    }
+};
+```
+
+This serializer works fine when you have control over the type's namespace. However, what about `boost::optional`, or `std::filesystem::path` (C++17)? Hijacking the `boost` namespace is pretty bad, and it's illegal to add something other than template specializations to `std`...
+
+To solve this, you need to add a specialization of `adl_serializer` to the `nlohmann` namespace, here's an example:
+
+```cpp
+// partial specialization (full specialization works too)
+namespace nlohmann {
+    template <typename T>
+    struct adl_serializer<boost::optional<T>> {
+        static void to_json(json& j, const boost::optional<T>& opt) {
+            if (opt == boost::none) {
+                j = nullptr;
+            } else {
+              j = *opt; // this will call adl_serializer<T>::to_json which will
+                        // find the free function to_json in T's namespace!
+            }
+        }
+
+        static void from_json(const json& j, boost::optional<T>& opt) {
+            if (!j.is_null()) {
+                opt = j.get<T>(); // same as above, but with 
+                                  // adl_serializer<T>::from_json
+            }
+        }
+    };
+}
+```
+
+#### How can I use `get()` for non-default constructible/non-copyable types?
+
+There is a way, if your type is [MoveConstructible](http://en.cppreference.com/w/cpp/concept/MoveConstructible). You will need to specialize the `adl_serializer` as well, but with a special `from_json` overload:
+
+```cpp
+struct move_only_type {
+    move_only_type() = delete;
+    move_only_type(int ii): i(ii) {}
+    move_only_type(const move_only_type&) = delete;
+    move_only_type(move_only_type&&) = default;
+    
+    int i;
+};
+
+namespace nlohmann {
+    template <>
+    struct adl_serializer<move_only_type> {
+        // note: the return type is no longer 'void', and the method only takes
+        // one argument
+        static move_only_type from_json(const json& j) {
+            return {j.get<int>()};
+        }
+        
+        // Here's the catch! You must provide a to_json method! Otherwise you
+        // will not be able to convert move_only_type to json, since you fully
+        // specialized adl_serializer on that type
+        static void to_json(json& j, move_only_type t) {
+            j = t.i;
+        }
+    };
+}
+```
+
+#### Can I write my own serializer? (Advanced use)
+
+Yes. You might want to take a look at [`unit-udt.cpp`](https://github.com/nlohmann/json/blob/develop/test/src/unit-udt.cpp) in the test suite, to see a few examples.
+
+If you write your own serializer, you'll need to do a few things:
+
+* use a different `basic_json` alias than `nlohmann::json` (the last template parameter of `basic_json` is the `JSONSerializer`)
+* use your `basic_json` alias (or a template parameter) in all your `to_json`/`from_json` methods
+* use `nlohmann::to_json` and `nlohmann::from_json` when you need ADL
+
+Here is an example, without simplifications, that only accepts types with a size <= 32, and uses ADL.
+
+```cpp
+// You should use void as a second template argument
+// if you don't need compile-time checks on T
+template<typename T, typename SFINAE = typename std::enable_if<sizeof(T) <= 32>::type>
+struct less_than_32_serializer {
+    template <typename BasicJsonType>
+    static void to_json(BasicJsonType& j, T value) {
+        // we want to use ADL, and call the correct to_json overload
+        using nlohmann::to_json; // this method is called by adl_serializer,
+                                 // this is where the magic happens
+        to_json(j, value);
+    }
+    
+    template <typename BasicJsonType>
+    static void from_json(const BasicJsonType& j, T& value) {
+        // same thing here
+        using nlohmann::from_json;
+        from_json(j, value);
+    }
+};
+```
+
+Be **very** careful when reimplementing your serializer, you can stack overflow if you don't pay attention:
+
+```cpp
+template <typename T, void>
+struct bad_serializer
+{
+    template <typename BasicJsonType>
+    static void to_json(BasicJsonType& j, const T& value) {
+      // this calls BasicJsonType::json_serializer<T>::to_json(j, value);
+      // if BasicJsonType::json_serializer == bad_serializer ... oops!
+      j = value;
+    }
+    
+    template <typename BasicJsonType>
+    static void to_json(const BasicJsonType& j, T& value) {
+      // this calls BasicJsonType::json_serializer<T>::from_json(j, value);
+      // if BasicJsonType::json_serializer == bad_serializer ... oops!
+      value = j.template get<T>(); // oops!
+    }
+};
+```
+
 ### Binary formats (CBOR and MessagePack)
 
 Though JSON is a ubiquitous data format, it is not a very compact format suitable for data exchange, for instance over a network. Hence, the library supports [CBOR](http://cbor.io) (Concise Binary Object Representation) and [MessagePack](http://msgpack.org) to efficiently encode JSON values to byte vectors and to decode such vectors.
@@ -546,7 +765,7 @@ I deeply appreciate the help of the following people.
 - [Eric Cornelius](https://github.com/EricMCornelius) pointed out a bug in the handling with NaN and infinity values. He also improved the performance of the string escaping.
 - [易思龙](https://github.com/likebeta) implemented a conversion from anonymous enums.
 - [kepkin](https://github.com/kepkin) patiently pushed forward the support for Microsoft Visual studio.
-- [gregmarr](https://github.com/gregmarr) simplified the implementation of reverse iterators and helped with numerous hints and improvements.
+- [gregmarr](https://github.com/gregmarr) simplified the implementation of reverse iterators and helped with numerous hints and improvements. In particular, he pushed forward the implementation of user-defined types.
 - [Caio Luppi](https://github.com/caiovlp) fixed a bug in the Unicode handling.
 - [dariomt](https://github.com/dariomt) fixed some typos in the examples.
 - [Daniel Frey](https://github.com/d-frey) cleaned up some pointers and implemented exception-safe memory allocation.
@@ -574,7 +793,7 @@ I deeply appreciate the help of the following people.
 - [duncanwerner](https://github.com/duncanwerner) found a really embarrassing performance regression in the 2.0.0 release.
 - [Damien](https://github.com/dtoma) fixed one of the last conversion warnings.
 - [Thomas Braun](https://github.com/t-b) fixed a warning in a test case.
-- [Théo DELRIEU](https://github.com/theodelrieu) patiently and constructively oversaw the long way toward [iterator-range parsing](https://github.com/nlohmann/json/issues/290).
+- [Théo DELRIEU](https://github.com/theodelrieu) patiently and constructively oversaw the long way toward [iterator-range parsing](https://github.com/nlohmann/json/issues/290). He also implemented the magic behind the serialization/deserialization of user-defined types.
 - [Stefan](https://github.com/5tefan) fixed a minor issue in the documentation.
 - [Vasil Dimov](https://github.com/vasild) fixed the documentation regarding conversions from `std::multiset`.
 - [ChristophJud](https://github.com/ChristophJud) overworked the CMake files to ease project inclusion.
@@ -588,6 +807,9 @@ I deeply appreciate the help of the following people.
 - [Bosswestfalen](https://github.com/Bosswestfalen) merged two iterator classes into a smaller one.
 - [Daniel599](https://github.com/Daniel599) helped to get Travis execute the tests with Clang's sanitizers.
 - [Jonathan Lee](https://github.com/vjon) fixed an example in the README file.
+- [gnzlbg](https://github.com/gnzlbg) supported the implementation of user-defined types.
+- [Alexej Harm](https://github.com/qis) helped to get the user-defined types working with Visual Studio.
+- [Jared Grubb](https://github.com/jaredgrubb) supported the implementation of user-defined types.
 
 Thanks a lot for helping out! Please [let me know](mailto:mail@nlohmann.me) if I forgot someone.
 
@@ -611,10 +833,11 @@ Thanks a lot for helping out! Please [let me know](mailto:mail@nlohmann.me) if I
 To compile and run the tests, you need to execute
 
 ```sh
-$ make check
+$ make json_unit -Ctest
+$ ./test/json_unit "*""
 
 ===============================================================================
-All tests passed (11202040 assertions in 44 test cases)
+All tests passed (11202052 assertions in 47 test cases)
 ```
 
 Alternatively, you can use [CMake](https://cmake.org) and run
diff --git a/doc/Doxyfile b/doc/Doxyfile
index 5064a0a0..4d511af3 100644
--- a/doc/Doxyfile
+++ b/doc/Doxyfile
@@ -109,7 +109,7 @@ RECURSIVE              = NO
 EXCLUDE                =
 EXCLUDE_SYMLINKS       = NO
 EXCLUDE_PATTERNS       =
-EXCLUDE_SYMBOLS        = nlohmann::anonymous_namespace
+EXCLUDE_SYMBOLS        = nlohmann::detail
 EXAMPLE_PATH           = examples
 EXAMPLE_PATTERNS       =
 EXAMPLE_RECURSIVE      = NO
diff --git a/doc/examples/basic_json__CompatibleArrayType.cpp b/doc/examples/basic_json__CompatibleArrayType.cpp
deleted file mode 100644
index 26a1a101..00000000
--- a/doc/examples/basic_json__CompatibleArrayType.cpp
+++ /dev/null
@@ -1,58 +0,0 @@
-#include <json.hpp>
-#include <deque>
-#include <list>
-#include <forward_list>
-#include <set>
-#include <unordered_set>
-
-using json = nlohmann::json;
-
-int main()
-{
-    // create an array from std::vector
-    std::vector<int> c_vector {1, 2, 3, 4};
-    json j_vec(c_vector);
-
-    // create an array from std::deque
-    std::deque<double> c_deque {1.2, 2.3, 3.4, 5.6};
-    json j_deque(c_deque);
-
-    // create an array from std::list
-    std::list<bool> c_list {true, true, false, true};
-    json j_list(c_list);
-
-    // create an array from std::forward_list
-    std::forward_list<int64_t> c_flist {12345678909876, 23456789098765, 34567890987654, 45678909876543};
-    json j_flist(c_flist);
-
-    // create an array from std::array
-    std::array<unsigned long, 4> c_array {{1, 2, 3, 4}};
-    json j_array(c_array);
-
-    // create an array from std::set
-    std::set<std::string> c_set {"one", "two", "three", "four", "one"};
-    json j_set(c_set); // only one entry for "one" is used
-
-    // create an array from std::unordered_set
-    std::unordered_set<std::string> c_uset {"one", "two", "three", "four", "one"};
-    json j_uset(c_uset); // only one entry for "one" is used
-
-    // create an array from std::multiset
-    std::multiset<std::string> c_mset {"one", "two", "one", "four"};
-    json j_mset(c_mset); // both entries for "one" are used
-
-    // create an array from std::unordered_multiset
-    std::unordered_multiset<std::string> c_umset {"one", "two", "one", "four"};
-    json j_umset(c_umset); // both entries for "one" are used
-
-    // serialize the JSON arrays
-    std::cout << j_vec << '\n';
-    std::cout << j_deque << '\n';
-    std::cout << j_list << '\n';
-    std::cout << j_flist << '\n';
-    std::cout << j_array << '\n';
-    std::cout << j_set << '\n';
-    std::cout << j_uset << '\n';
-    std::cout << j_mset << '\n';
-    std::cout << j_umset << '\n';
-}
diff --git a/doc/examples/basic_json__CompatibleArrayType.link b/doc/examples/basic_json__CompatibleArrayType.link
deleted file mode 100644
index 95ecb050..00000000
--- a/doc/examples/basic_json__CompatibleArrayType.link
+++ /dev/null
@@ -1 +0,0 @@
-<a target="_blank" href="http://melpon.org/wandbox/permlink/3BIhBw91FUVuHE1D"><b>online</b></a>
\ No newline at end of file
diff --git a/doc/examples/basic_json__CompatibleArrayType.output b/doc/examples/basic_json__CompatibleArrayType.output
deleted file mode 100644
index 428505a1..00000000
--- a/doc/examples/basic_json__CompatibleArrayType.output
+++ /dev/null
@@ -1,9 +0,0 @@
-[1,2,3,4]
-[1.2,2.3,3.4,5.6]
-[true,true,false,true]
-[12345678909876,23456789098765,34567890987654,45678909876543]
-[1,2,3,4]
-["four","one","three","two"]
-["four","three","two","one"]
-["four","one","one","two"]
-["four","two","one","one"]
diff --git a/doc/examples/basic_json__CompatibleIntegerNumberType.cpp b/doc/examples/basic_json__CompatibleIntegerNumberType.cpp
deleted file mode 100644
index 50e751d1..00000000
--- a/doc/examples/basic_json__CompatibleIntegerNumberType.cpp
+++ /dev/null
@@ -1,27 +0,0 @@
-#include <json.hpp>
-
-using json = nlohmann::json;
-
-int main()
-{
-    // create values of different integer types
-    short n42 = 42;
-    int n23 = 23;
-    long n1024 = 1024;
-    int_least32_t n17 = 17;
-    uint8_t n8 = 8;
-
-    // create JSON numbers
-    json j42(n42);
-    json j23(n23);
-    json j1024(n1024);
-    json j17(n17);
-    json j8(n8);
-
-    // serialize the JSON numbers
-    std::cout << j42 << '\n';
-    std::cout << j23 << '\n';
-    std::cout << j1024 << '\n';
-    std::cout << j17 << '\n';
-    std::cout << j8 << '\n';
-}
diff --git a/doc/examples/basic_json__CompatibleIntegerNumberType.link b/doc/examples/basic_json__CompatibleIntegerNumberType.link
deleted file mode 100644
index 7a37e9eb..00000000
--- a/doc/examples/basic_json__CompatibleIntegerNumberType.link
+++ /dev/null
@@ -1 +0,0 @@
-<a target="_blank" href="http://melpon.org/wandbox/permlink/PcMzhcu2RpD7KSwr"><b>online</b></a>
\ No newline at end of file
diff --git a/doc/examples/basic_json__CompatibleIntegerNumberType.output b/doc/examples/basic_json__CompatibleIntegerNumberType.output
deleted file mode 100644
index c7f24d63..00000000
--- a/doc/examples/basic_json__CompatibleIntegerNumberType.output
+++ /dev/null
@@ -1,5 +0,0 @@
-42
-23
-1024
-17
-8
diff --git a/doc/examples/basic_json__CompatibleNumberFloatType.cpp b/doc/examples/basic_json__CompatibleNumberFloatType.cpp
deleted file mode 100644
index 6f8d3f67..00000000
--- a/doc/examples/basic_json__CompatibleNumberFloatType.cpp
+++ /dev/null
@@ -1,21 +0,0 @@
-#include <json.hpp>
-
-using json = nlohmann::json;
-
-int main()
-{
-    // create values of different floating-point types
-    float f42 = 42.23;
-    float f_nan = 1.0f / 0.0f;
-    double f23 = 23.42;
-
-    // create JSON numbers
-    json j42(f42);
-    json j_nan(f_nan);
-    json j23(f23);
-
-    // serialize the JSON numbers
-    std::cout << j42 << '\n';
-    std::cout << j_nan << '\n';
-    std::cout << j23 << '\n';
-}
diff --git a/doc/examples/basic_json__CompatibleNumberFloatType.link b/doc/examples/basic_json__CompatibleNumberFloatType.link
deleted file mode 100644
index 9fbc7317..00000000
--- a/doc/examples/basic_json__CompatibleNumberFloatType.link
+++ /dev/null
@@ -1 +0,0 @@
-<a target="_blank" href="http://melpon.org/wandbox/permlink/2TCYWSrOxnR05AZI"><b>online</b></a>
\ No newline at end of file
diff --git a/doc/examples/basic_json__CompatibleNumberFloatType.output b/doc/examples/basic_json__CompatibleNumberFloatType.output
deleted file mode 100644
index 64bb796c..00000000
--- a/doc/examples/basic_json__CompatibleNumberFloatType.output
+++ /dev/null
@@ -1,3 +0,0 @@
-42.2299995422363
-null
-23.42
diff --git a/doc/examples/basic_json__CompatibleObjectType.cpp b/doc/examples/basic_json__CompatibleObjectType.cpp
deleted file mode 100644
index d284b697..00000000
--- a/doc/examples/basic_json__CompatibleObjectType.cpp
+++ /dev/null
@@ -1,41 +0,0 @@
-#include <json.hpp>
-#include <unordered_map>
-
-using json = nlohmann::json;
-
-int main()
-{
-    // create an object from std::map
-    std::map<std::string, int> c_map
-    {
-        {"one", 1}, {"two", 2}, {"three", 3}
-    };
-    json j_map(c_map);
-
-    // create an object from std::unordered_map
-    std::unordered_map<const char*, double> c_umap
-    {
-        {"one", 1.2}, {"two", 2.3}, {"three", 3.4}
-    };
-    json j_umap(c_umap);
-
-    // create an object from std::multimap
-    std::multimap<std::string, bool> c_mmap
-    {
-        {"one", true}, {"two", true}, {"three", false}, {"three", true}
-    };
-    json j_mmap(c_mmap); // only one entry for key "three" is used
-
-    // create an object from std::unordered_multimap
-    std::unordered_multimap<std::string, bool> c_ummap
-    {
-        {"one", true}, {"two", true}, {"three", false}, {"three", true}
-    };
-    json j_ummap(c_ummap); // only one entry for key "three" is used
-
-    // serialize the JSON objects
-    std::cout << j_map << '\n';
-    std::cout << j_umap << '\n';
-    std::cout << j_mmap << '\n';
-    std::cout << j_ummap << '\n';
-}
diff --git a/doc/examples/basic_json__CompatibleObjectType.link b/doc/examples/basic_json__CompatibleObjectType.link
deleted file mode 100644
index 7512fb35..00000000
--- a/doc/examples/basic_json__CompatibleObjectType.link
+++ /dev/null
@@ -1 +0,0 @@
-<a target="_blank" href="http://melpon.org/wandbox/permlink/JzLCMcSXNsh4uVWa"><b>online</b></a>
\ No newline at end of file
diff --git a/doc/examples/basic_json__CompatibleObjectType.output b/doc/examples/basic_json__CompatibleObjectType.output
deleted file mode 100644
index c70f7184..00000000
--- a/doc/examples/basic_json__CompatibleObjectType.output
+++ /dev/null
@@ -1,4 +0,0 @@
-{"one":1,"three":3,"two":2}
-{"one":1.2,"three":3.4,"two":2.3}
-{"one":true,"three":false,"two":true}
-{"one":true,"three":false,"two":true}
diff --git a/doc/examples/basic_json__CompatibleStringType.cpp b/doc/examples/basic_json__CompatibleStringType.cpp
deleted file mode 100644
index a0f3b4f6..00000000
--- a/doc/examples/basic_json__CompatibleStringType.cpp
+++ /dev/null
@@ -1,15 +0,0 @@
-#include <json.hpp>
-
-using json = nlohmann::json;
-
-int main()
-{
-    // create a string value
-    std::string s = "The quick brown fox jumps over the lazy dog.";
-
-    // create a JSON string value
-    json j = s;
-
-    // serialize the JSON string
-    std::cout << j << '\n';
-}
diff --git a/doc/examples/basic_json__CompatibleStringType.link b/doc/examples/basic_json__CompatibleStringType.link
deleted file mode 100644
index 351d6c0c..00000000
--- a/doc/examples/basic_json__CompatibleStringType.link
+++ /dev/null
@@ -1 +0,0 @@
-<a target="_blank" href="http://melpon.org/wandbox/permlink/b9hbCY8zfOiTOuoK"><b>online</b></a>
\ No newline at end of file
diff --git a/doc/examples/basic_json__CompatibleStringType.output b/doc/examples/basic_json__CompatibleStringType.output
deleted file mode 100644
index 1316dd98..00000000
--- a/doc/examples/basic_json__CompatibleStringType.output
+++ /dev/null
@@ -1 +0,0 @@
-"The quick brown fox jumps over the lazy dog."
diff --git a/doc/examples/basic_json__CompatibleType.cpp b/doc/examples/basic_json__CompatibleType.cpp
new file mode 100644
index 00000000..ff564a72
--- /dev/null
+++ b/doc/examples/basic_json__CompatibleType.cpp
@@ -0,0 +1,211 @@
+#include <json.hpp>
+#include <deque>
+#include <list>
+#include <forward_list>
+#include <set>
+#include <unordered_map>
+#include <unordered_set>
+
+using json = nlohmann::json;
+
+int main()
+{
+    // ============
+    // object types
+    // ============
+
+    // create an object from an object_t value
+    json::object_t object_value = { {"one", 1}, {"two", 2} };
+    json j_object_t(object_value);
+
+    // create an object from std::map
+    std::map<std::string, int> c_map
+    {
+        {"one", 1}, {"two", 2}, {"three", 3}
+    };
+    json j_map(c_map);
+
+    // create an object from std::unordered_map
+    std::unordered_map<const char*, double> c_umap
+    {
+        {"one", 1.2}, {"two", 2.3}, {"three", 3.4}
+    };
+    json j_umap(c_umap);
+
+    // create an object from std::multimap
+    std::multimap<std::string, bool> c_mmap
+    {
+        {"one", true}, {"two", true}, {"three", false}, {"three", true}
+    };
+    json j_mmap(c_mmap); // only one entry for key "three" is used
+
+    // create an object from std::unordered_multimap
+    std::unordered_multimap<std::string, bool> c_ummap
+    {
+        {"one", true}, {"two", true}, {"three", false}, {"three", true}
+    };
+    json j_ummap(c_ummap); // only one entry for key "three" is used
+
+    // serialize the JSON objects
+    std::cout << j_object_t << '\n';
+    std::cout << j_map << '\n';
+    std::cout << j_umap << '\n';
+    std::cout << j_mmap << '\n';
+    std::cout << j_ummap << "\n\n";
+
+
+    // ===========
+    // array types
+    // ===========
+
+    // create an array from an array_t value
+    json::array_t array_value = {"one", "two", 3, 4.5, false};
+    json j_array_t(array_value);
+
+    // create an array from std::vector
+    std::vector<int> c_vector {1, 2, 3, 4};
+    json j_vec(c_vector);
+
+    // create an array from std::deque
+    std::deque<double> c_deque {1.2, 2.3, 3.4, 5.6};
+    json j_deque(c_deque);
+
+    // create an array from std::list
+    std::list<bool> c_list {true, true, false, true};
+    json j_list(c_list);
+
+    // create an array from std::forward_list
+    std::forward_list<int64_t> c_flist {12345678909876, 23456789098765, 34567890987654, 45678909876543};
+    json j_flist(c_flist);
+
+    // create an array from std::array
+    std::array<unsigned long, 4> c_array {{1, 2, 3, 4}};
+    json j_array(c_array);
+
+    // create an array from std::set
+    std::set<std::string> c_set {"one", "two", "three", "four", "one"};
+    json j_set(c_set); // only one entry for "one" is used
+
+    // create an array from std::unordered_set
+    std::unordered_set<std::string> c_uset {"one", "two", "three", "four", "one"};
+    json j_uset(c_uset); // only one entry for "one" is used
+
+    // create an array from std::multiset
+    std::multiset<std::string> c_mset {"one", "two", "one", "four"};
+    json j_mset(c_mset); // both entries for "one" are used
+
+    // create an array from std::unordered_multiset
+    std::unordered_multiset<std::string> c_umset {"one", "two", "one", "four"};
+    json j_umset(c_umset); // both entries for "one" are used
+
+    // serialize the JSON arrays
+    std::cout << j_array_t << '\n';
+    std::cout << j_vec << '\n';
+    std::cout << j_deque << '\n';
+    std::cout << j_list << '\n';
+    std::cout << j_flist << '\n';
+    std::cout << j_array << '\n';
+    std::cout << j_set << '\n';
+    std::cout << j_uset << '\n';
+    std::cout << j_mset << '\n';
+    std::cout << j_umset << "\n\n";
+
+
+    // ============
+    // string types
+    // ============
+
+    // create string from a string_t value
+    json::string_t string_value = "The quick brown fox jumps over the lazy dog.";
+    json j_string_t(string_value);
+
+    // create a JSON string directly from a string literal
+    json j_string_literal("The quick brown fox jumps over the lazy dog.");
+
+    // create string from std::string
+    std::string s_stdstring = "The quick brown fox jumps over the lazy dog.";
+    json j_stdstring(s_stdstring);
+
+    // serialize the JSON strings
+    std::cout << j_string_t << '\n';
+    std::cout << j_string_literal << '\n';
+    std::cout << j_stdstring << "\n\n";
+
+
+    // ============
+    // number types
+    // ============
+
+    // create a JSON number from number_integer_t
+    json::number_integer_t value_integer_t = -42;
+    json j_integer_t(value_integer_t);
+
+    // create a JSON number from number_unsigned_t
+    json::number_integer_t value_unsigned_t = 17;
+    json j_unsigned_t(value_unsigned_t);
+
+    // create a JSON number from an anonymous enum
+    enum { enum_value = 17 };
+    json j_enum(enum_value);
+
+    // create values of different integer types
+    short n_short = 42;
+    int n_int = -23;
+    long n_long = 1024;
+    int_least32_t n_int_least32_t = -17;
+    uint8_t n_uint8_t = 8;
+
+    // create (integer) JSON numbers
+    json j_short(n_short);
+    json j_int(n_int);
+    json j_long(n_long);
+    json j_int_least32_t(n_int_least32_t);
+    json j_uint8_t(n_uint8_t);
+
+    // create values of different floating-point types
+    json::number_float_t v_ok = 3.141592653589793;
+    json::number_float_t v_nan = NAN;
+    json::number_float_t v_infinity = INFINITY;
+
+    // create values of different floating-point types
+    float n_float = 42.23;
+    float n_float_nan = 1.0f / 0.0f;
+    double n_double = 23.42;
+
+    // create (floating point) JSON numbers
+    json j_ok(v_ok);
+    json j_nan(v_nan);
+    json j_infinity(v_infinity);
+    json j_float(n_float);
+    json j_float_nan(n_float_nan);
+    json j_double(n_double);
+
+    // serialize the JSON numbers
+    std::cout << j_integer_t << '\n';
+    std::cout << j_unsigned_t << '\n';
+    std::cout << j_enum << '\n';
+    std::cout << j_short << '\n';
+    std::cout << j_int << '\n';
+    std::cout << j_long << '\n';
+    std::cout << j_int_least32_t << '\n';
+    std::cout << j_uint8_t << '\n';
+    std::cout << j_ok << '\n';
+    std::cout << j_nan << '\n';
+    std::cout << j_infinity << '\n';
+    std::cout << j_float << '\n';
+    std::cout << j_float_nan << '\n';
+    std::cout << j_double << "\n\n";
+
+
+    // =============
+    // boolean types
+    // =============
+
+    // create boolean values
+    json j_truth = true;
+    json j_falsity = false;
+
+    // serialize the JSON booleans
+    std::cout << j_truth << '\n';
+    std::cout << j_falsity << '\n';
+}
diff --git a/doc/examples/basic_json__CompatibleType.link b/doc/examples/basic_json__CompatibleType.link
new file mode 100644
index 00000000..a78f01bb
--- /dev/null
+++ b/doc/examples/basic_json__CompatibleType.link
@@ -0,0 +1 @@
+<a target="_blank" href="http://melpon.org/wandbox/permlink/VM7W2kpE7sIYJ5DW"><b>online</b></a>
\ No newline at end of file
diff --git a/doc/examples/basic_json__CompatibleType.output b/doc/examples/basic_json__CompatibleType.output
new file mode 100644
index 00000000..d69ff44f
--- /dev/null
+++ b/doc/examples/basic_json__CompatibleType.output
@@ -0,0 +1,38 @@
+{"one":1,"two":2}
+{"one":1,"three":3,"two":2}
+{"one":1.2,"three":3.4,"two":2.3}
+{"one":true,"three":false,"two":true}
+{"one":true,"three":false,"two":true}
+
+["one","two",3,4.5,false]
+[1,2,3,4]
+[1.2,2.3,3.4,5.6]
+[true,true,false,true]
+[12345678909876,23456789098765,34567890987654,45678909876543]
+[1,2,3,4]
+["four","one","three","two"]
+["four","three","two","one"]
+["four","one","one","two"]
+["four","two","one","one"]
+
+"The quick brown fox jumps over the lazy dog."
+"The quick brown fox jumps over the lazy dog."
+"The quick brown fox jumps over the lazy dog."
+
+-42
+17
+17
+42
+-23
+1024
+-17
+8
+3.14159265358979
+null
+null
+42.2299995422363
+null
+23.42
+
+true
+false
diff --git a/doc/examples/basic_json__array_t.cpp b/doc/examples/basic_json__array_t.cpp
deleted file mode 100644
index 1bb6931b..00000000
--- a/doc/examples/basic_json__array_t.cpp
+++ /dev/null
@@ -1,15 +0,0 @@
-#include <json.hpp>
-
-using json = nlohmann::json;
-
-int main()
-{
-    // create an array_t value
-    json::array_t value = {"one", "two", 3, 4.5, false};
-
-    // create a JSON array from the value
-    json j(value);
-
-    // serialize the JSON array
-    std::cout << j << '\n';
-}
diff --git a/doc/examples/basic_json__array_t.link b/doc/examples/basic_json__array_t.link
deleted file mode 100644
index 70c9cb8c..00000000
--- a/doc/examples/basic_json__array_t.link
+++ /dev/null
@@ -1 +0,0 @@
-<a target="_blank" href="http://melpon.org/wandbox/permlink/dTbSNAvl6TqrMEAn"><b>online</b></a>
\ No newline at end of file
diff --git a/doc/examples/basic_json__array_t.output b/doc/examples/basic_json__array_t.output
deleted file mode 100644
index d379a756..00000000
--- a/doc/examples/basic_json__array_t.output
+++ /dev/null
@@ -1 +0,0 @@
-["one","two",3,4.5,false]
diff --git a/doc/examples/basic_json__boolean_t.cpp b/doc/examples/basic_json__boolean_t.cpp
deleted file mode 100644
index 38f014e0..00000000
--- a/doc/examples/basic_json__boolean_t.cpp
+++ /dev/null
@@ -1,14 +0,0 @@
-#include <json.hpp>
-
-using json = nlohmann::json;
-
-int main()
-{
-    // create boolean values
-    json j_truth = true;
-    json j_falsity = false;
-
-    // serialize the JSON booleans
-    std::cout << j_truth << '\n';
-    std::cout << j_falsity << '\n';
-}
diff --git a/doc/examples/basic_json__boolean_t.link b/doc/examples/basic_json__boolean_t.link
deleted file mode 100644
index c64e1fc4..00000000
--- a/doc/examples/basic_json__boolean_t.link
+++ /dev/null
@@ -1 +0,0 @@
-<a target="_blank" href="http://melpon.org/wandbox/permlink/VmVl9pyrQp8LyOnU"><b>online</b></a>
\ No newline at end of file
diff --git a/doc/examples/basic_json__boolean_t.output b/doc/examples/basic_json__boolean_t.output
deleted file mode 100644
index da29283a..00000000
--- a/doc/examples/basic_json__boolean_t.output
+++ /dev/null
@@ -1,2 +0,0 @@
-true
-false
diff --git a/doc/examples/basic_json__const_int.cpp b/doc/examples/basic_json__const_int.cpp
deleted file mode 100644
index 7e38544b..00000000
--- a/doc/examples/basic_json__const_int.cpp
+++ /dev/null
@@ -1,15 +0,0 @@
-#include <json.hpp>
-
-using json = nlohmann::json;
-
-int main()
-{
-    // an anonymous enum
-    enum { t = 17 };
-
-    // create a JSON number from the enum
-    json j(t);
-
-    // serialize the JSON numbers
-    std::cout << j << '\n';
-}
diff --git a/doc/examples/basic_json__const_int.link b/doc/examples/basic_json__const_int.link
deleted file mode 100644
index 68a9e235..00000000
--- a/doc/examples/basic_json__const_int.link
+++ /dev/null
@@ -1 +0,0 @@
-<a target="_blank" href="http://melpon.org/wandbox/permlink/3xQ1qy7BT9OrSSCo"><b>online</b></a>
\ No newline at end of file
diff --git a/doc/examples/basic_json__const_int.output b/doc/examples/basic_json__const_int.output
deleted file mode 100644
index 98d9bcb7..00000000
--- a/doc/examples/basic_json__const_int.output
+++ /dev/null
@@ -1 +0,0 @@
-17
diff --git a/doc/examples/basic_json__number_float_t.cpp b/doc/examples/basic_json__number_float_t.cpp
deleted file mode 100644
index 92533b7d..00000000
--- a/doc/examples/basic_json__number_float_t.cpp
+++ /dev/null
@@ -1,21 +0,0 @@
-#include <json.hpp>
-
-using json = nlohmann::json;
-
-int main()
-{
-    // create values of different floating-point types
-    json::number_float_t v_ok = 3.141592653589793;
-    json::number_float_t v_nan = NAN;
-    json::number_float_t v_infinity = INFINITY;
-
-    // create JSON numbers
-    json j_ok(v_ok);
-    json j_nan(v_nan);
-    json j_infinity(v_infinity);
-
-    // serialize the JSON numbers
-    std::cout << j_ok << '\n';
-    std::cout << j_nan << '\n';
-    std::cout << j_infinity << '\n';
-}
diff --git a/doc/examples/basic_json__number_float_t.link b/doc/examples/basic_json__number_float_t.link
deleted file mode 100644
index 47aa2553..00000000
--- a/doc/examples/basic_json__number_float_t.link
+++ /dev/null
@@ -1 +0,0 @@
-<a target="_blank" href="http://melpon.org/wandbox/permlink/OTgOxjIAKFvxpFdm"><b>online</b></a>
\ No newline at end of file
diff --git a/doc/examples/basic_json__number_float_t.output b/doc/examples/basic_json__number_float_t.output
deleted file mode 100644
index 964a7b1f..00000000
--- a/doc/examples/basic_json__number_float_t.output
+++ /dev/null
@@ -1,3 +0,0 @@
-3.14159265358979
-null
-null
diff --git a/doc/examples/basic_json__number_integer_t.cpp b/doc/examples/basic_json__number_integer_t.cpp
deleted file mode 100644
index 1078f360..00000000
--- a/doc/examples/basic_json__number_integer_t.cpp
+++ /dev/null
@@ -1,14 +0,0 @@
-#include <json.hpp>
-
-using json = nlohmann::json;
-
-int main()
-{
-    // create a JSON number from number_integer_t
-    json::number_integer_t value = 42;
-
-    json j(value);
-
-    // serialize the JSON numbers
-    std::cout << j << '\n';
-}
diff --git a/doc/examples/basic_json__number_integer_t.link b/doc/examples/basic_json__number_integer_t.link
deleted file mode 100644
index 5d4499b5..00000000
--- a/doc/examples/basic_json__number_integer_t.link
+++ /dev/null
@@ -1 +0,0 @@
-<a target="_blank" href="http://melpon.org/wandbox/permlink/cCQRCvjXdRM9YpT5"><b>online</b></a>
\ No newline at end of file
diff --git a/doc/examples/basic_json__number_integer_t.output b/doc/examples/basic_json__number_integer_t.output
deleted file mode 100644
index d81cc071..00000000
--- a/doc/examples/basic_json__number_integer_t.output
+++ /dev/null
@@ -1 +0,0 @@
-42
diff --git a/doc/examples/basic_json__object_t.cpp b/doc/examples/basic_json__object_t.cpp
deleted file mode 100644
index 39e2fcc0..00000000
--- a/doc/examples/basic_json__object_t.cpp
+++ /dev/null
@@ -1,15 +0,0 @@
-#include <json.hpp>
-
-using json = nlohmann::json;
-
-int main()
-{
-    // create an object_t value
-    json::object_t value = { {"one", 1}, {"two", 2} };
-
-    // create a JSON object from the value
-    json j(value);
-
-    // serialize the JSON object
-    std::cout << j << '\n';
-}
diff --git a/doc/examples/basic_json__object_t.link b/doc/examples/basic_json__object_t.link
deleted file mode 100644
index 2e07a3ef..00000000
--- a/doc/examples/basic_json__object_t.link
+++ /dev/null
@@ -1 +0,0 @@
-<a target="_blank" href="http://melpon.org/wandbox/permlink/DgtHcz5L6JphTOGJ"><b>online</b></a>
\ No newline at end of file
diff --git a/doc/examples/basic_json__object_t.output b/doc/examples/basic_json__object_t.output
deleted file mode 100644
index 62376d83..00000000
--- a/doc/examples/basic_json__object_t.output
+++ /dev/null
@@ -1 +0,0 @@
-{"one":1,"two":2}
diff --git a/doc/examples/basic_json__string_t.cpp b/doc/examples/basic_json__string_t.cpp
deleted file mode 100644
index 3205f623..00000000
--- a/doc/examples/basic_json__string_t.cpp
+++ /dev/null
@@ -1,15 +0,0 @@
-#include <json.hpp>
-
-using json = nlohmann::json;
-
-int main()
-{
-    // create an string_t value
-    json::string_t value = "The quick brown fox jumps over the lazy doc";
-
-    // create a JSON string from the value
-    json j(value);
-
-    // serialize the JSON array
-    std::cout << j << '\n';
-}
diff --git a/doc/examples/basic_json__string_t.link b/doc/examples/basic_json__string_t.link
deleted file mode 100644
index d7d02f2f..00000000
--- a/doc/examples/basic_json__string_t.link
+++ /dev/null
@@ -1 +0,0 @@
-<a target="_blank" href="http://melpon.org/wandbox/permlink/cwNYP1q2mT8CFLTk"><b>online</b></a>
\ No newline at end of file
diff --git a/doc/examples/basic_json__string_t.output b/doc/examples/basic_json__string_t.output
deleted file mode 100644
index 89990044..00000000
--- a/doc/examples/basic_json__string_t.output
+++ /dev/null
@@ -1 +0,0 @@
-"The quick brown fox jumps over the lazy doc"
diff --git a/doc/examples/basic_json__string_t_value_type.cpp b/doc/examples/basic_json__string_t_value_type.cpp
deleted file mode 100644
index 5379ca06..00000000
--- a/doc/examples/basic_json__string_t_value_type.cpp
+++ /dev/null
@@ -1,12 +0,0 @@
-#include <json.hpp>
-
-using json = nlohmann::json;
-
-int main()
-{
-    // create a JSON string directly from a string literal
-    json j("The quick brown fox jumps over the lazy doc");
-
-    // serialize the JSON array
-    std::cout << j << '\n';
-}
diff --git a/doc/examples/basic_json__string_t_value_type.link b/doc/examples/basic_json__string_t_value_type.link
deleted file mode 100644
index 56908768..00000000
--- a/doc/examples/basic_json__string_t_value_type.link
+++ /dev/null
@@ -1 +0,0 @@
-<a target="_blank" href="http://melpon.org/wandbox/permlink/AtV4zVErfLwkileg"><b>online</b></a>
\ No newline at end of file
diff --git a/doc/examples/basic_json__string_t_value_type.output b/doc/examples/basic_json__string_t_value_type.output
deleted file mode 100644
index 89990044..00000000
--- a/doc/examples/basic_json__string_t_value_type.output
+++ /dev/null
@@ -1 +0,0 @@
-"The quick brown fox jumps over the lazy doc"
diff --git a/src/json.hpp b/src/json.hpp
index 2c1fd658..6909553a 100644
--- a/src/json.hpp
+++ b/src/json.hpp
@@ -39,6 +39,7 @@ SOFTWARE.
 #include <cstdint> // int64_t, uint64_t
 #include <cstdlib> // strtod, strtof, strtold, strtoul
 #include <cstring> // strlen
+#include <forward_list> // forward_list
 #include <functional> // function, hash, less
 #include <initializer_list> // initializer_list
 #include <iomanip> // setw
@@ -107,37 +108,840 @@ SOFTWARE.
 namespace nlohmann
 {
 
-
 /*!
 @brief unnamed namespace with internal helper functions
+
+This namespace collects some functions that could not be defined inside the
+@ref basic_json class.
+
+@since version 2.1.0
+*/
+namespace detail
+{
+///////////////////////////
+// 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 basic_json::is_null(),
+@ref basic_json::is_object(), @ref basic_json::is_array(),
+@ref basic_json::is_string(), @ref basic_json::is_boolean(),
+@ref basic_json::is_number() (with @ref basic_json::is_number_integer(),
+@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()),
+@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and
+@ref basic_json::is_structured() rely on it.
+
+@note There are three enumeration entries (number_integer, number_unsigned, and
+number_float), because the library distinguishes these three types for numbers:
+@ref basic_json::number_unsigned_t is used for unsigned integers,
+@ref basic_json::number_integer_t is used for signed integers, and
+@ref basic_json::number_float_t is used for floating-point numbers or to
+approximate integers which do not fit in the limits of their respective type.
+
+@sa @ref basic_json::basic_json(const value_t value_type) -- create a JSON
+value with the default value for a given type
+
 @since version 1.0.0
 */
-namespace
+enum class value_t : uint8_t
 {
+    null,            ///< null value
+    object,          ///< object (unordered set of name/value pairs)
+    array,           ///< array (ordered collection of values)
+    string,          ///< string value
+    boolean,         ///< boolean value
+    number_integer,  ///< number value (signed integer)
+    number_unsigned, ///< number value (unsigned integer)
+    number_float,    ///< number value (floating-point)
+    discarded        ///< discarded by the the parser callback function
+};
+
+/*!
+@brief 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
+*/
+inline bool operator<(const value_t lhs, const value_t rhs) noexcept
+{
+    static constexpr std::array<uint8_t, 8> order = {{
+            0, // null
+            3, // object
+            4, // array
+            5, // string
+            1, // boolean
+            2, // integer
+            2, // unsigned
+            2, // float
+        }
+    };
+
+    // discarded values are not comparable
+    if (lhs == value_t::discarded or rhs == value_t::discarded)
+    {
+        return false;
+    }
+
+    return order[static_cast<std::size_t>(lhs)] <
+           order[static_cast<std::size_t>(rhs)];
+}
+
+
+/////////////
+// helpers //
+/////////////
+
+// alias templates to reduce boilerplate
+template<bool B, typename T = void>
+using enable_if_t = typename std::enable_if<B, T>::type;
+
+template<typename T>
+using uncvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
+
+// taken from http://stackoverflow.com/a/26936864/266378
+template<typename T>
+using is_unscoped_enum =
+    std::integral_constant<bool, std::is_convertible<T, int>::value and
+    std::is_enum<T>::value>;
+
+/*
+Implementation of two C++17 constructs: conjunction, negation. This is needed
+to avoid evaluating all the traits in a condition
+
+For example: not std::is_same<void, T>::value and has_value_type<T>::value
+will not compile when T = void (on MSVC at least). Whereas
+conjunction<negation<std::is_same<void, T>>, has_value_type<T>>::value will
+stop evaluating if negation<...>::value == false
+
+Please note that those constructs must be used with caution, since symbols can
+become very long quickly (which can slow down compilation and cause MSVC
+internal compiler errors). Only use it when you have to (see example ahead).
+*/
+template<class...> struct conjunction : std::true_type {};
+template<class B1> struct conjunction<B1> : B1 {};
+template<class B1, class... Bn>
+struct conjunction<B1, Bn...> : std::conditional<bool(B1::value), conjunction<Bn...>, B1>::type {};
+
+template<class B> struct negation : std::integral_constant < bool, !B::value > {};
+
+// dispatch utility (taken from ranges-v3)
+template<unsigned N> struct priority_tag : priority_tag < N - 1 > {};
+template<> struct priority_tag<0> {};
+
+
+//////////////////
+// constructors //
+//////////////////
+
+template<value_t> struct external_constructor;
+
+template<>
+struct external_constructor<value_t::boolean>
+{
+    template<typename BasicJsonType>
+    static void construct(BasicJsonType& j, typename BasicJsonType::boolean_t b) noexcept
+    {
+        j.m_type = value_t::boolean;
+        j.m_value = b;
+        j.assert_invariant();
+    }
+};
+
+template<>
+struct external_constructor<value_t::string>
+{
+    template<typename BasicJsonType>
+    static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s)
+    {
+        j.m_type = value_t::string;
+        j.m_value = s;
+        j.assert_invariant();
+    }
+};
+
+template<>
+struct external_constructor<value_t::number_float>
+{
+    template<typename BasicJsonType>
+    static void construct(BasicJsonType& j, typename BasicJsonType::number_float_t val) noexcept
+    {
+        // replace infinity and NAN by null
+        if (not std::isfinite(val))
+        {
+            j = BasicJsonType{};
+        }
+        else
+        {
+            j.m_type = value_t::number_float;
+            j.m_value = val;
+        }
+        j.assert_invariant();
+    }
+};
+
+template<>
+struct external_constructor<value_t::number_unsigned>
+{
+    template<typename BasicJsonType>
+    static void construct(BasicJsonType& j, typename BasicJsonType::number_unsigned_t val) noexcept
+    {
+        j.m_type = value_t::number_unsigned;
+        j.m_value = val;
+        j.assert_invariant();
+    }
+};
+
+template<>
+struct external_constructor<value_t::number_integer>
+{
+    template<typename BasicJsonType>
+    static void construct(BasicJsonType& j, typename BasicJsonType::number_integer_t val) noexcept
+    {
+        j.m_type = value_t::number_integer;
+        j.m_value = val;
+        j.assert_invariant();
+    }
+};
+
+template<>
+struct external_constructor<value_t::array>
+{
+    template<typename BasicJsonType>
+    static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr)
+    {
+        j.m_type = value_t::array;
+        j.m_value = arr;
+        j.assert_invariant();
+    }
+
+    template<typename BasicJsonType, typename CompatibleArrayType,
+             enable_if_t<not std::is_same<CompatibleArrayType,
+                                          typename BasicJsonType::array_t>::value,
+                         int> = 0>
+    static void construct(BasicJsonType& j, const CompatibleArrayType& arr)
+    {
+        using std::begin;
+        using std::end;
+        j.m_type = value_t::array;
+        j.m_value.array = j.template create<typename BasicJsonType::array_t>(begin(arr), end(arr));
+        j.assert_invariant();
+    }
+};
+
+template<>
+struct external_constructor<value_t::object>
+{
+    template<typename BasicJsonType>
+    static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj)
+    {
+        j.m_type = value_t::object;
+        j.m_value = obj;
+        j.assert_invariant();
+    }
+
+    template<typename BasicJsonType, typename CompatibleObjectType,
+             enable_if_t<not std::is_same<CompatibleObjectType,
+                                          typename BasicJsonType::object_t>::value,
+                         int> = 0>
+    static void construct(BasicJsonType& j, const CompatibleObjectType& obj)
+    {
+        using std::begin;
+        using std::end;
+
+        j.m_type = value_t::object;
+        j.m_value.object = j.template create<typename BasicJsonType::object_t>(begin(obj), end(obj));
+        j.assert_invariant();
+    }
+};
+
+
+////////////////////////
+// has_/is_ functions //
+////////////////////////
+
 /*!
 @brief Helper to determine whether there's a key_type for T.
 
-Thus helper is used to tell associative containers apart from other containers
+This helper is used to tell associative containers apart from other containers
 such as sequence containers. For instance, `std::map` passes the test as it
 contains a `mapped_type`, whereas `std::vector` fails the test.
 
 @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
-{
-  private:
-    template <typename U, typename = typename U::mapped_type>
-    static int detect(U&&);
+#define NLOHMANN_JSON_HAS_HELPER(type)                                        \
+    template<typename T> struct has_##type {                                  \
+    private:                                                                  \
+        template<typename U, typename = typename U::type>                     \
+        static int detect(U &&);                                              \
+        static void detect(...);                                              \
+    public:                                                                   \
+        static constexpr bool value =                                         \
+                std::is_integral<decltype(detect(std::declval<T>()))>::value; \
+    }
 
-    static void detect(...);
-  public:
-    static constexpr bool value =
-        std::is_integral<decltype(detect(std::declval<T>()))>::value;
+NLOHMANN_JSON_HAS_HELPER(mapped_type);
+NLOHMANN_JSON_HAS_HELPER(key_type);
+NLOHMANN_JSON_HAS_HELPER(value_type);
+NLOHMANN_JSON_HAS_HELPER(iterator);
+
+#undef NLOHMANN_JSON_HAS_HELPER
+
+
+template<bool B, class RealType, class CompatibleObjectType>
+struct is_compatible_object_type_impl : std::false_type {};
+
+template<class RealType, class CompatibleObjectType>
+struct is_compatible_object_type_impl<true, RealType, CompatibleObjectType>
+{
+    static constexpr auto value =
+        std::is_constructible<typename RealType::key_type,
+        typename CompatibleObjectType::key_type>::value and
+        std::is_constructible<typename RealType::mapped_type,
+        typename CompatibleObjectType::mapped_type>::value;
+};
+
+template<class BasicJsonType, class CompatibleObjectType>
+struct is_compatible_object_type
+{
+    static auto constexpr value = is_compatible_object_type_impl <
+                                  conjunction<negation<std::is_same<void, CompatibleObjectType>>,
+                                  has_mapped_type<CompatibleObjectType>,
+                                  has_key_type<CompatibleObjectType>>::value,
+                                  typename BasicJsonType::object_t, CompatibleObjectType >::value;
+};
+
+template<typename BasicJsonType, typename T>
+struct is_basic_json_nested_type
+{
+    static auto constexpr value = std::is_same<T, typename BasicJsonType::iterator>::value or
+                                  std::is_same<T, typename BasicJsonType::const_iterator>::value or
+                                  std::is_same<T, typename BasicJsonType::reverse_iterator>::value or
+                                  std::is_same<T, typename BasicJsonType::const_reverse_iterator>::value or
+                                  std::is_same<T, typename BasicJsonType::json_pointer>::value;
+};
+
+template<class BasicJsonType, class CompatibleArrayType>
+struct is_compatible_array_type
+{
+    static auto constexpr value =
+        conjunction<negation<std::is_same<void, CompatibleArrayType>>,
+        negation<is_compatible_object_type<
+        BasicJsonType, CompatibleArrayType>>,
+        negation<std::is_constructible<typename BasicJsonType::string_t,
+        CompatibleArrayType>>,
+        negation<is_basic_json_nested_type<BasicJsonType, CompatibleArrayType>>,
+        has_value_type<CompatibleArrayType>,
+        has_iterator<CompatibleArrayType>>::value;
+};
+
+template<bool, typename, typename>
+struct is_compatible_integer_type_impl : std::false_type {};
+
+template<typename RealIntegerType, typename CompatibleNumberIntegerType>
+struct is_compatible_integer_type_impl<true, RealIntegerType, CompatibleNumberIntegerType>
+{
+    // is there an assert somewhere on overflows?
+    using RealLimits = std::numeric_limits<RealIntegerType>;
+    using CompatibleLimits = std::numeric_limits<CompatibleNumberIntegerType>;
+
+    static constexpr auto value =
+        std::is_constructible<RealIntegerType,
+        CompatibleNumberIntegerType>::value and
+        CompatibleLimits::is_integer and
+        RealLimits::is_signed == CompatibleLimits::is_signed;
+};
+
+template<typename RealIntegerType, typename CompatibleNumberIntegerType>
+struct is_compatible_integer_type
+{
+    static constexpr auto value =
+        is_compatible_integer_type_impl <
+        std::is_integral<CompatibleNumberIntegerType>::value and
+        not std::is_same<bool, CompatibleNumberIntegerType>::value,
+        RealIntegerType, CompatibleNumberIntegerType > ::value;
+};
+
+
+// trait checking if JSONSerializer<T>::from_json(json const&, udt&) exists
+template<typename BasicJsonType, typename T>
+struct has_from_json
+{
+  private:
+    // also check the return type of from_json
+    template<typename U, typename = enable_if_t<std::is_same<void, decltype(uncvref_t<U>::from_json(
+                 std::declval<BasicJsonType>(), std::declval<T&>()))>::value>>
+    static int detect(U&&);
+    static void detect(...);
+
+  public:
+    static constexpr bool value = std::is_integral<decltype(
+                                      detect(std::declval<typename BasicJsonType::template json_serializer<T, void>>()))>::value;
+};
+
+// This trait checks if JSONSerializer<T>::from_json(json const&) exists
+// this overload is used for non-default-constructible user-defined-types
+template<typename BasicJsonType, typename T>
+struct has_non_default_from_json
+{
+  private:
+    template <
+        typename U,
+        typename = enable_if_t<std::is_same<
+                                   T, decltype(uncvref_t<U>::from_json(std::declval<BasicJsonType>()))>::value >>
+    static int detect(U&&);
+    static void detect(...);
+
+  public:
+    static constexpr bool value = std::is_integral<decltype(detect(
+                                      std::declval<typename BasicJsonType::template json_serializer<T, void>>()))>::value;
+};
+
+// This trait checks if BasicJsonType::json_serializer<T>::to_json exists
+template<typename BasicJsonType, typename T>
+struct has_to_json
+{
+  private:
+    template<typename U, typename = decltype(uncvref_t<U>::to_json(
+                 std::declval<BasicJsonType&>(), std::declval<T>()))>
+    static int detect(U&&);
+    static void detect(...);
+
+  public:
+    static constexpr bool value = std::is_integral<decltype(detect(
+                                      std::declval<typename BasicJsonType::template json_serializer<T, void>>()))>::value;
+};
+
+
+/////////////
+// to_json //
+/////////////
+
+template<typename BasicJsonType>
+void to_json(BasicJsonType& j, typename BasicJsonType::boolean_t b) noexcept
+{
+    external_constructor<value_t::boolean>::construct(j, b);
+}
+
+template<typename BasicJsonType, typename CompatibleString,
+         enable_if_t<std::is_constructible<typename BasicJsonType::string_t,
+                     CompatibleString>::value, int> = 0>
+void to_json(BasicJsonType& j, const CompatibleString& s)
+{
+    external_constructor<value_t::string>::construct(j, s);
+}
+
+template<typename BasicJsonType, typename FloatType,
+         enable_if_t<std::is_floating_point<FloatType>::value, int> = 0>
+void to_json(BasicJsonType& j, FloatType val) noexcept
+{
+    external_constructor<value_t::number_float>::construct(j, static_cast<typename BasicJsonType::number_float_t>(val));
+}
+
+template <
+    typename BasicJsonType, typename CompatibleNumberUnsignedType,
+    enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_unsigned_t,
+                CompatibleNumberUnsignedType>::value, int> = 0 >
+void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept
+{
+    external_constructor<value_t::number_unsigned>::construct(j, static_cast<typename BasicJsonType::number_unsigned_t>(val));
+}
+
+template <
+    typename BasicJsonType, typename CompatibleNumberIntegerType,
+    enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_integer_t,
+                CompatibleNumberIntegerType>::value, int> = 0 >
+void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept
+{
+    external_constructor<value_t::number_integer>::construct(j, static_cast<typename BasicJsonType::number_integer_t>(val));
+}
+
+template<typename BasicJsonType, typename UnscopedEnumType,
+         enable_if_t<is_unscoped_enum<UnscopedEnumType>::value, int> = 0>
+void to_json(BasicJsonType& j, UnscopedEnumType e) noexcept
+{
+    external_constructor<value_t::number_integer>::construct(j, e);
+}
+
+template <
+    typename BasicJsonType, typename CompatibleArrayType,
+    enable_if_t <
+        is_compatible_array_type<BasicJsonType, CompatibleArrayType>::value or
+        std::is_same<typename BasicJsonType::array_t, CompatibleArrayType>::value,
+        int > = 0 >
+void to_json(BasicJsonType& j, const  CompatibleArrayType& arr)
+{
+    external_constructor<value_t::array>::construct(j, arr);
+}
+
+template <
+    typename BasicJsonType, typename CompatibleObjectType,
+    enable_if_t<is_compatible_object_type<BasicJsonType, CompatibleObjectType>::value,
+                int> = 0 >
+void to_json(BasicJsonType& j, const  CompatibleObjectType& arr)
+{
+    external_constructor<value_t::object>::construct(j, arr);
+}
+
+
+///////////////
+// from_json //
+///////////////
+
+// overloads for basic_json template parameters
+template<typename BasicJsonType, typename ArithmeticType,
+         enable_if_t<std::is_arithmetic<ArithmeticType>::value and
+                     not std::is_same<ArithmeticType,
+                                      typename BasicJsonType::boolean_t>::value,
+                     int> = 0>
+void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val)
+{
+    switch (static_cast<value_t>(j))
+    {
+        case value_t::number_unsigned:
+        {
+            val = static_cast<ArithmeticType>(
+                      *j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>());
+            break;
+        }
+        case value_t::number_integer:
+        {
+            val = static_cast<ArithmeticType>(
+                      *j.template get_ptr<const typename BasicJsonType::number_integer_t*>());
+            break;
+        }
+        case value_t::number_float:
+        {
+            val = static_cast<ArithmeticType>(
+                      *j.template get_ptr<const typename BasicJsonType::number_float_t*>());
+            break;
+        }
+        default:
+        {
+            JSON_THROW(
+                std::domain_error("type must be number, but is " + j.type_name()));
+        }
+    }
+}
+
+template<typename BasicJsonType>
+void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b)
+{
+    if (not j.is_boolean())
+    {
+        JSON_THROW(std::domain_error("type must be boolean, but is " + j.type_name()));
+    }
+    b = *j.template get_ptr<const typename BasicJsonType::boolean_t*>();
+}
+
+template<typename BasicJsonType>
+void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s)
+{
+    if (not j.is_string())
+    {
+        JSON_THROW(std::domain_error("type must be string, but is " + j.type_name()));
+    }
+    s = *j.template get_ptr<const typename BasicJsonType::string_t*>();
+}
+
+template<typename BasicJsonType>
+void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val)
+{
+    get_arithmetic_value(j, val);
+}
+
+template<typename BasicJsonType>
+void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val)
+{
+    get_arithmetic_value(j, val);
+}
+
+template<typename BasicJsonType>
+void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val)
+{
+    get_arithmetic_value(j, val);
+}
+
+template<typename BasicJsonType, typename UnscopedEnumType,
+         enable_if_t<is_unscoped_enum<UnscopedEnumType>::value, int> = 0>
+void from_json(const BasicJsonType& j, UnscopedEnumType& e)
+{
+    typename std::underlying_type<UnscopedEnumType>::type val = e;
+    get_arithmetic_value(j, val);
+    e = static_cast<UnscopedEnumType>(val);
+}
+
+template<typename BasicJsonType>
+void from_json(const BasicJsonType& j, typename BasicJsonType::array_t& arr)
+{
+    if (not j.is_array())
+    {
+        JSON_THROW(std::domain_error("type must be array, but is " + j.type_name()));
+    }
+    arr = *j.template get_ptr<const typename BasicJsonType::array_t*>();
+}
+
+// forward_list doesn't have an insert method
+template<typename BasicJsonType, typename T, typename Allocator>
+void from_json(const BasicJsonType& j, std::forward_list<T, Allocator>& l)
+{
+    // do not perform the check when user wants to retrieve jsons
+    // (except when it's null.. ?)
+    if (j.is_null())
+    {
+        JSON_THROW(std::domain_error("type must be array, but is " + j.type_name()));
+    }
+    if (not std::is_same<T, BasicJsonType>::value)
+    {
+        if (not j.is_array())
+        {
+            JSON_THROW(std::domain_error("type must be array, but is " + j.type_name()));
+        }
+    }
+    for (auto it = j.rbegin(), end = j.rend(); it != end; ++it)
+    {
+        l.push_front(it->template get<T>());
+    }
+}
+
+template<typename BasicJsonType, typename CompatibleArrayType>
+void from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<0>)
+{
+    using std::begin;
+    using std::end;
+
+    std::transform(j.begin(), j.end(),
+                   std::inserter(arr, end(arr)), [](const BasicJsonType & i)
+    {
+        // get<BasicJsonType>() returns *this, this won't call a from_json
+        // method when value_type is BasicJsonType
+        return i.template get<typename CompatibleArrayType::value_type>();
+    });
+}
+
+template<typename BasicJsonType, typename CompatibleArrayType>
+auto from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<1>)
+-> decltype(
+    arr.reserve(std::declval<typename CompatibleArrayType::size_type>()),
+    void())
+{
+    using std::begin;
+    using std::end;
+
+    arr.reserve(j.size());
+    std::transform(
+        j.begin(), j.end(), std::inserter(arr, end(arr)), [](const BasicJsonType & i)
+    {
+        // get<BasicJsonType>() returns *this, this won't call a from_json
+        // method when value_type is BasicJsonType
+        return i.template get<typename CompatibleArrayType::value_type>();
+    });
+}
+
+template<typename BasicJsonType, typename CompatibleArrayType,
+         enable_if_t<is_compatible_array_type<BasicJsonType, CompatibleArrayType>::value and
+                     not std::is_same<typename BasicJsonType::array_t, CompatibleArrayType>::value, int> = 0>
+void from_json(const BasicJsonType& j, CompatibleArrayType& arr)
+{
+    if (j.is_null())
+    {
+        JSON_THROW(std::domain_error("type must be array, but is " + j.type_name()));
+    }
+
+    // when T == BasicJsonType, do not check if value_t is correct
+    if (not std::is_same<typename CompatibleArrayType::value_type, BasicJsonType>::value)
+    {
+        if (not j.is_array())
+        {
+            JSON_THROW(std::domain_error("type must be array, but is " + j.type_name()));
+        }
+    }
+    from_json_array_impl(j, arr, priority_tag<1> {});
+}
+
+template<typename BasicJsonType, typename CompatibleObjectType,
+         enable_if_t<is_compatible_object_type<BasicJsonType, CompatibleObjectType>::value, int> = 0>
+void from_json(const BasicJsonType& j, CompatibleObjectType& obj)
+{
+    if (not j.is_object())
+    {
+        JSON_THROW(std::domain_error("type must be object, but is " + j.type_name()));
+    }
+
+    auto inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>();
+    using std::begin;
+    using std::end;
+    // we could avoid the assignment, but this might require a for loop, which
+    // might be less efficient than the container constructor for some
+    // containers (would it?)
+    obj = CompatibleObjectType(begin(*inner_object), end(*inner_object));
+}
+
+// overload for arithmetic types, not chosen for basic_json template arguments
+// (BooleanType, etc..); note: Is it really necessary to provide explicit
+// overloads for boolean_t etc. in case of a custom BooleanType which is not
+// an arithmetic type?
+template<typename BasicJsonType, typename ArithmeticType,
+         enable_if_t <
+             std::is_arithmetic<ArithmeticType>::value and
+             not std::is_same<ArithmeticType, typename BasicJsonType::number_unsigned_t>::value and
+             not std::is_same<ArithmeticType, typename BasicJsonType::number_integer_t>::value and
+             not std::is_same<ArithmeticType, typename BasicJsonType::number_float_t>::value and
+             not std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value,
+             int> = 0>
+void from_json(const BasicJsonType& j, ArithmeticType& val)
+{
+    switch (static_cast<value_t>(j))
+    {
+        case value_t::number_unsigned:
+        {
+            val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>());
+            break;
+        }
+        case value_t::number_integer:
+        {
+            val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t*>());
+            break;
+        }
+        case value_t::number_float:
+        {
+            val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t*>());
+            break;
+        }
+        case value_t::boolean:
+        {
+            val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::boolean_t*>());
+            break;
+        }
+        default:
+        {
+            JSON_THROW(std::domain_error("type must be number, but is " + j.type_name()));
+        }
+    }
+}
+
+struct to_json_fn
+{
+  private:
+    template<typename BasicJsonType, typename T>
+    auto call(BasicJsonType& j, T&& val, priority_tag<1>) const noexcept(noexcept(to_json(j, std::forward<T>(val))))
+    -> decltype(to_json(j, std::forward<T>(val)), void())
+    {
+        return to_json(j, std::forward<T>(val));
+    }
+
+    template<typename BasicJsonType, typename T>
+    void call(BasicJsonType&, T&&, priority_tag<0>) const noexcept
+    {
+        static_assert(sizeof(BasicJsonType) == 0,
+                      "could not find to_json() method in T's namespace");
+    }
+
+  public:
+    template<typename BasicJsonType, typename T>
+    void operator()(BasicJsonType& j, T&& val) const
+    noexcept(noexcept(std::declval<to_json_fn>().call(j, std::forward<T>(val), priority_tag<1> {})))
+    {
+        return call(j, std::forward<T>(val), priority_tag<1> {});
+    }
+};
+
+struct from_json_fn
+{
+  private:
+    template<typename BasicJsonType, typename T>
+    auto call(const BasicJsonType& j, T& val, priority_tag<1>) const
+    noexcept(noexcept(from_json(j, val)))
+    -> decltype(from_json(j, val), void())
+    {
+        return from_json(j, val);
+    }
+
+    template<typename BasicJsonType, typename T>
+    void call(const BasicJsonType&, T&, priority_tag<0>) const noexcept
+    {
+        static_assert(sizeof(BasicJsonType) == 0,
+                      "could not find from_json() method in T's namespace");
+    }
+
+  public:
+    template<typename BasicJsonType, typename T>
+    void operator()(const BasicJsonType& j, T& val) const
+    noexcept(noexcept(std::declval<from_json_fn>().call(j, val, priority_tag<1> {})))
+    {
+        return call(j, val, priority_tag<1> {});
+    }
+};
+
+// taken from ranges-v3
+template<typename T>
+struct static_const
+{
+    static constexpr T value{};
+};
+
+template<typename T>
+constexpr T static_const<T>::value;
+} // namespace detail
+
+
+/// namespace to hold default `to_json` / `from_json` functions
+namespace
+{
+constexpr const auto& to_json = detail::static_const<detail::to_json_fn>::value;
+constexpr const auto& from_json = detail::static_const<detail::from_json_fn>::value;
+}
+
+
+/*!
+@brief default JSONSerializer template argument
+
+This serializer ignores the template arguments and uses ADL
+([argument-dependent lookup](http://en.cppreference.com/w/cpp/language/adl))
+for serialization.
+*/
+template<typename = void, typename = void>
+struct adl_serializer
+{
+    /*!
+    @brief convert a JSON value to any value type
+
+    This function is usually called by the `get()` function of the
+    @ref basic_json class (either explicit or via conversion operators).
+
+    @param[in] j         JSON value to read from
+    @param[in, out] val  value to write to
+    */
+    template<typename BasicJsonType, typename ValueType>
+    static void from_json(BasicJsonType&& j, ValueType& val) noexcept(
+        noexcept(::nlohmann::from_json(std::forward<BasicJsonType>(j), val)))
+    {
+        ::nlohmann::from_json(std::forward<BasicJsonType>(j), val);
+    }
+
+    /*!
+    @brief convert any value type to a JSON value
+
+    This function is usually called by the constructors of the @ref basic_json
+    class.
+
+    @param[in, out] j  JSON value to write to
+    @param[in] val     value to read from
+    */
+    template<typename BasicJsonType, typename ValueType>
+    static void to_json(BasicJsonType& j, ValueType&& val) noexcept(
+        noexcept(::nlohmann::to_json(j, std::forward<ValueType>(val))))
+    {
+        ::nlohmann::to_json(j, std::forward<ValueType>(val));
+    }
 };
 
-} // namespace
 
 /*!
 @brief a class to store JSON values
@@ -158,6 +962,8 @@ default; will be used in @ref number_integer_t)
 default; will be used in @ref number_float_t)
 @tparam AllocatorType type of the allocator to use (`std::allocator` by
 default)
+@tparam JSONSerializer the serializer to resolve internal calls to `to_json()`
+and `from_json()` (@ref adl_serializer by default)
 
 @requirement The class satisfies the following concept requirements:
 - Basic
@@ -226,21 +1032,26 @@ template <
     class NumberIntegerType = std::int64_t,
     class NumberUnsignedType = std::uint64_t,
     class NumberFloatType = double,
-    template<typename U> class AllocatorType = std::allocator
+    template<typename U> class AllocatorType = std::allocator,
+    template<typename T, typename SFINAE = void> class JSONSerializer = adl_serializer
     >
 class basic_json
 {
   private:
+    template<detail::value_t> friend struct detail::external_constructor;
     /// workaround type for MSVC
     using basic_json_t = basic_json<ObjectType, ArrayType, StringType,
           BooleanType, NumberIntegerType, NumberUnsignedType, NumberFloatType,
-          AllocatorType>;
+          AllocatorType, JSONSerializer>;
 
   public:
+    using value_t = detail::value_t;
     // forward declarations
     template<typename U> class iter_impl;
     template<typename Base> class json_reverse_iterator;
     class json_pointer;
+    template<typename T, typename SFINAE>
+    using json_serializer = JSONSerializer<T, SFINAE>;
 
     /////////////////////
     // container types //
@@ -787,47 +1598,6 @@ class basic_json
 
     /// @}
 
-
-    ///////////////////////////
-    // 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
-    };
-
-
   private:
 
     /// helper for exception-safe object creation
@@ -1120,18 +1890,6 @@ class basic_json
     @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)
@@ -1165,473 +1923,69 @@ class basic_json
     }
 
     /*!
-    @brief create an object (explicit)
+    @brief create a JSON value
 
-    Create an object JSON value with a given content.
+    This is a "catch all" constructor for all compatible JSON types; that is,
+    types for which a `to_json()` method exsits. The constructor forwards the
+    parameter @a val to that method (to `json_serializer<U>::to_json` method
+    with `U = uncvref_t<CompatibleType>`, to be exact).
 
-    @param[in] val  a value for the object
+    Template type @a CompatibleType includes, but is not limited to, the
+    following types:
+    - **arrays**: @ref array_t and all kinds of compatible containers such as
+      `std::vector`, `std::deque`, `std::list`, `std::forward_list`,
+      `std::array`, `std::set`, `std::unordered_set`, `std::multiset`, and
+      `unordered_multiset` with a `value_type` from which a @ref basic_json
+      value can be constructed.
+    - **objects**: @ref object_t and all kinds of compatible associative
+      containers such as `std::map`, `std::unordered_map`, `std::multimap`,
+      and `std::unordered_multimap` with a `key_type` compatible to
+      @ref string_t and a `value_type` from which a @ref basic_json value can
+      be constructed.
+    - **strings**: @ref string_t, string literals, and all compatible string
+      containers can be used.
+    - **numbers**: @ref number_integer_t, @ref number_unsigned_t,
+      @ref number_float_t, and all convertible number types such as `int`,
+      `size_t`, `int64_t`, `float` or `double` can be used.
+    - **boolean**: @ref boolean_t / `bool` can be used.
 
-    @complexity Linear in the size of the passed @a val.
+    See the examples below.
 
-    @throw std::bad_alloc if allocation for object value fails
+    @tparam CompatibleType a type such that:
+    - @a CompatibleType is not derived from `std::istream`,
+    - @a CompatibleType is not @ref basic_json (to avoid hijacking copy/move
+         constructors),
+    - @a CompatibleType is not a @ref basic_json nested type (e.g.,
+         @ref json_pointer, @ref iterator, etc ...)
+    - @ref @ref json_serializer<U> has a
+         `to_json(basic_json_t&, CompatibleType&&)` method
 
-    @liveexample{The following code shows the constructor with an @ref
-    object_t parameter.,basic_json__object_t}
+    @tparam U = `uncvref_t<CompatibleType>`
 
-    @sa @ref basic_json(const CompatibleObjectType&) -- create an object value
-    from a compatible STL container
+    @param[in] val the value to be forwarded
 
-    @since version 1.0.0
-    */
-    basic_json(const object_t& val)
-        : m_type(value_t::object), m_value(val)
-    {
-        assert_invariant();
-    }
+    @complexity Usually linear in the size of the passed @a val, also
+                depending on the implementation of the called `to_json()`
+                method.
 
-    /*!
-    @brief create an object (implicit)
-
-    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
+    @throw what `json_serializer<U>::to_json()` throws
 
     @liveexample{The following code shows the constructor with several
-    compatible object type parameters.,basic_json__CompatibleObjectType}
+    compatible types.,basic_json__CompatibleType}
 
-    @sa @ref basic_json(const object_t&) -- create an object value
-
-    @since version 1.0.0
+    @since version 2.1.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
-
-    @complexity Linear in the size of the passed @a val.
-
-    @throw std::bad_alloc if allocation for array value fails
-
-    @liveexample{The following code shows the constructor with an @ref array_t
-    parameter.,basic_json__array_t}
-
-    @sa @ref basic_json(const CompatibleArrayType&) -- create an array value
-    from a compatible STL containers
-
-    @since version 1.0.0
-    */
-    basic_json(const array_t& val)
-        : m_type(value_t::array), m_value(val)
-    {
-        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.
-
-    @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.
-
-    @param[in] val  a value for the array
-
-    @complexity Linear in the size of the passed @a val.
-
-    @throw std::bad_alloc if allocation for array value fails
-
-    @liveexample{The following code shows the constructor with several
-    compatible array type parameters.,basic_json__CompatibleArrayType}
-
-    @sa @ref basic_json(const array_t&) -- create an array value
-
-    @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();
-    }
-
-    /*!
-    @brief create a string (explicit)
-
-    Create an string JSON value with a given content.
-
-    @param[in] val  a value for the string
-
-    @complexity Linear in the size of the passed @a val.
-
-    @throw std::bad_alloc if allocation for string value fails
-
-    @liveexample{The following code shows the constructor with an @ref
-    string_t parameter.,basic_json__string_t}
-
-    @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
-
-    @since version 1.0.0
-    */
-    basic_json(const string_t& val)
-        : m_type(value_t::string), m_value(val)
-    {
-        assert_invariant();
-    }
-
-    /*!
-    @brief create a string (explicit)
-
-    Create a string JSON value with a given content.
-
-    @param[in] val  a literal value for the string
-
-    @complexity Linear in the size of the passed @a val.
-
-    @throw std::bad_alloc if allocation for string value fails
-
-    @liveexample{The following code shows the constructor with string literal
-    parameter.,basic_json__string_t_value_type}
-
-    @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
-
-    @since version 1.0.0
-    */
-    basic_json(const typename string_t::value_type* val)
-        : basic_json(string_t(val))
-    {
-        assert_invariant();
-    }
-
-    /*!
-    @brief create a string (implicit)
-
-    Create a string JSON value with a given content.
-
-    @param[in] val  a value for the string
-
-    @tparam CompatibleStringType an string type which is compatible to @ref
-    string_t, for instance `std::string`.
-
-    @complexity Linear in the size of the passed @a val.
-
-    @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();
-    }
-
-    /*!
-    @brief create a boolean (explicit)
-
-    Creates a JSON boolean type from a given value.
-
-    @param[in] val  a boolean value to store
-
-    @complexity Constant.
-
-    @liveexample{The example below demonstrates boolean
-    values.,basic_json__boolean_t}
-
-    @since version 1.0.0
-    */
-    basic_json(boolean_t val) noexcept
-        : m_type(value_t::boolean), m_value(val)
-    {
-        assert_invariant();
-    }
-
-    /*!
-    @brief create an integer number (explicit)
-
-    Create an integer number JSON value with a given content.
-
-    @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  an integer to create a JSON number from
-
-    @complexity Constant.
-
-    @liveexample{The example below shows the construction of an integer
-    number value.,basic_json__number_integer_t}
-
-    @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
-    */
-    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 create an integer number from an enum type (explicit)
-
-    Create an integer number JSON value with a given content.
-
-    @param[in] val  an integer to create a JSON number from
-
-    @note This constructor allows to pass enums directly to a constructor. As
-    C++ has no way of specifying the type of an anonymous enum explicitly, we
-    can only rely on the fact that such values implicitly convert to int. As
-    int may already be the same type of number_integer_t, we may need to
-    switch off the constructor @ref basic_json(const number_integer_t).
-
-    @complexity Constant.
-
-    @liveexample{The example below shows the construction of an integer
-    number value from an anonymous enum.,basic_json__const_int}
-
-    @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))
+    template<typename CompatibleType, typename U = detail::uncvref_t<CompatibleType>,
+             detail::enable_if_t<not std::is_base_of<std::istream, U>::value and
+                                 not std::is_same<U, basic_json_t>::value and
+                                 not detail::is_basic_json_nested_type<
+                                     basic_json_t, U>::value and
+                                 detail::has_to_json<basic_json, U>::value,
+                                 int> = 0>
+    basic_json(CompatibleType && val) noexcept(noexcept(JSONSerializer<U>::to_json(
+                std::declval<basic_json_t&>(), std::forward<CompatibleType>(val))))
     {
+        JSONSerializer<U>::to_json(*this, std::forward<CompatibleType>(val));
         assert_invariant();
     }
 
@@ -2655,142 +3009,6 @@ class basic_json
     // value access //
     //////////////////
 
-    /// get an object (explicit)
-    template<class T, typename std::enable_if<
-                 std::is_convertible<typename object_t::key_type, typename T::key_type>::value and
-                 std::is_convertible<basic_json_t, typename T::mapped_type>::value, int>::type = 0>
-    T get_impl(T* /*unused*/) const
-    {
-        if (is_object())
-        {
-            return T(m_value.object->begin(), m_value.object->end());
-        }
-
-        JSON_THROW(std::domain_error("type must be object, but is " + type_name()));
-    }
-
-    /// get an object (explicit)
-    object_t get_impl(object_t* /*unused*/) const
-    {
-        if (is_object())
-        {
-            return *(m_value.object);
-        }
-
-        JSON_THROW(std::domain_error("type must be object, but is " + type_name()));
-    }
-
-    /// get an array (explicit)
-    template<class T, typename std::enable_if<
-                 std::is_convertible<basic_json_t, typename T::value_type>::value and
-                 not std::is_same<basic_json_t, typename T::value_type>::value and
-                 not std::is_arithmetic<T>::value and
-                 not std::is_convertible<std::string, T>::value and
-                 not has_mapped_type<T>::value, int>::type = 0>
-    T get_impl(T* /*unused*/) const
-    {
-        if (is_array())
-        {
-            T to_vector;
-            std::transform(m_value.array->begin(), m_value.array->end(),
-                           std::inserter(to_vector, to_vector.end()), [](basic_json i)
-            {
-                return i.get<typename T::value_type>();
-            });
-            return to_vector;
-        }
-
-        JSON_THROW(std::domain_error("type must be array, but is " + type_name()));
-    }
-
-    /// get an array (explicit)
-    template<class T, typename std::enable_if<
-                 std::is_convertible<basic_json_t, T>::value and
-                 not std::is_same<basic_json_t, T>::value, int>::type = 0>
-    std::vector<T> get_impl(std::vector<T>* /*unused*/) const
-    {
-        if (is_array())
-        {
-            std::vector<T> to_vector;
-            to_vector.reserve(m_value.array->size());
-            std::transform(m_value.array->begin(), m_value.array->end(),
-                           std::inserter(to_vector, to_vector.end()), [](basic_json i)
-            {
-                return i.get<T>();
-            });
-            return to_vector;
-        }
-
-        JSON_THROW(std::domain_error("type must be array, but is " + type_name()));
-    }
-
-    /// get an array (explicit)
-    template<class T, typename std::enable_if<
-                 std::is_same<basic_json, typename T::value_type>::value and
-                 not has_mapped_type<T>::value, int>::type = 0>
-    T get_impl(T* /*unused*/) const
-    {
-        if (is_array())
-        {
-            return T(m_value.array->begin(), m_value.array->end());
-        }
-
-        JSON_THROW(std::domain_error("type must be array, but is " + type_name()));
-    }
-
-    /// get an array (explicit)
-    array_t get_impl(array_t* /*unused*/) const
-    {
-        if (is_array())
-        {
-            return *(m_value.array);
-        }
-
-        JSON_THROW(std::domain_error("type must be array, but is " + type_name()));
-    }
-
-    /// get a string (explicit)
-    template<typename T, typename std::enable_if<
-                 std::is_convertible<string_t, T>::value, int>::type = 0>
-    T get_impl(T* /*unused*/) const
-    {
-        if (is_string())
-        {
-            return *m_value.string;
-        }
-
-        JSON_THROW(std::domain_error("type must be string, but is " + type_name()));
-    }
-
-    /// get a number (explicit)
-    template<typename T, typename std::enable_if<
-                 std::is_arithmetic<T>::value, int>::type = 0>
-    T get_impl(T* /*unused*/) const
-    {
-        switch (m_type)
-        {
-            case value_t::number_integer:
-            {
-                return static_cast<T>(m_value.number_integer);
-            }
-
-            case value_t::number_unsigned:
-            {
-                return static_cast<T>(m_value.number_unsigned);
-            }
-
-            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
     {
@@ -2918,26 +3136,63 @@ class basic_json
     }
 
   public:
-
     /// @name value access
     /// Direct access to the stored value of a JSON value.
     /// @{
 
+    /*!
+    @brief get special-case overload
+
+    This overloads avoids a lot of template boilerplate, it can be seen as the
+    identity method
+
+    @tparam BasicJsonType == @ref basic_json
+
+    @return a copy of *this
+
+    @complexity Constant.
+
+    @since version 2.1.0
+    */
+    template <
+        typename BasicJsonType,
+        detail::enable_if_t<std::is_same<typename std::remove_const<BasicJsonType>::type,
+                                         basic_json_t>::value,
+                            int> = 0 >
+    basic_json get() const
+    {
+        return *this;
+    }
+
     /*!
     @brief get a value (explicit)
 
-    Explicit type conversion between the JSON value and a compatible value.
+    Explicit type conversion between the JSON value and a compatible value
+    which is [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible)
+    and [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible).
+    The value is converted by calling the @ref json_serializer<ValueType>
+    `from_json()` method.
 
-    @tparam ValueType non-pointer type compatible to the JSON value, for
-    instance `int` for JSON integer numbers, `bool` for JSON booleans, or
-    `std::vector` types for JSON arrays
+    The function is equivalent to executing
+    @code {.cpp}
+    ValueType ret;
+    JSONSerializer<ValueType>::from_json(*this, ret);
+    return ret;
+    @endcode
 
-    @return copy of the JSON value, converted to type @a ValueType
+    This overloads is chosen if:
+    - @a ValueType is not @ref basic_json,
+    - @ref json_serializer<ValueType> has a `from_json()` method of the form
+      `void from_json(const @ref basic_json&, ValueType&)`, and
+    - @ref json_serializer<ValueType> does not have a `from_json()` method of
+      the form `ValueType from_json(const @ref basic_json&)`
 
-    @throw std::domain_error in case passed type @a ValueType is incompatible
-    to JSON; example: `"type must be object, but is null"`
+    @tparam ValueTypeCV the provided value type
+    @tparam ValueType the returned value type
 
-    @complexity Linear in the size of the JSON value.
+    @return copy of the JSON value, converted to @a ValueType
+
+    @throw what @ref json_serializer<ValueType> `from_json()` method throws
 
     @liveexample{The example below shows several conversions from JSON values
     to other types. There a few things to note: (1) Floating-point numbers can
@@ -2946,21 +3201,75 @@ class basic_json
     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
+    @since version 2.1.0
     */
-    template<typename ValueType, typename std::enable_if<
-                 not std::is_pointer<ValueType>::value, int>::type = 0>
-    ValueType get() const
+    template <
+        typename ValueTypeCV,
+        typename ValueType = detail::uncvref_t<ValueTypeCV>,
+        detail::enable_if_t <
+            not std::is_same<basic_json_t, ValueType>::value and
+            detail::has_from_json<basic_json_t, ValueType>::value and
+            not detail::has_non_default_from_json<basic_json_t, ValueType>::value,
+            int > = 0 >
+    ValueType get() const noexcept(noexcept(
+                                       JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>(), std::declval<ValueType&>())))
     {
-        return get_impl(static_cast<ValueType*>(nullptr));
+        // we cannot static_assert on ValueTypeCV being non-const, because
+        // there is support for get<const basic_json_t>(), which is why we
+        // still need the uncvref
+        static_assert(not std::is_reference<ValueTypeCV>::value,
+                      "get() cannot be used with reference types, you might want to use get_ref()");
+        static_assert(std::is_default_constructible<ValueType>::value,
+                      "types must be DefaultConstructible when used with get()");
+
+        ValueType ret;
+        JSONSerializer<ValueType>::from_json(*this, ret);
+        return ret;
+    }
+
+    /*!
+    @brief get a value (explicit); special case
+
+    Explicit type conversion between the JSON value and a compatible value
+    which is **not** [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible)
+    and **not** [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible).
+    The value is converted by calling the @ref json_serializer<ValueType>
+    `from_json()` method.
+
+    The function is equivalent to executing
+    @code {.cpp}
+    return JSONSerializer<ValueTypeCV>::from_json(*this);
+    @endcode
+
+    This overloads is chosen if:
+    - @a ValueType is not @ref basic_json and
+    - @ref json_serializer<ValueType> has a `from_json()` method of the form
+      `ValueType from_json(const @ref basic_json&)`
+
+    @note If @ref json_serializer<ValueType> has both overloads of
+    `from_json()`, this one is chosen.
+
+    @tparam ValueTypeCV the provided value type
+    @tparam ValueType the returned value type
+
+    @return copy of the JSON value, converted to @a ValueType
+
+    @throw what @ref json_serializer<ValueType> `from_json()` method throws
+
+    @since version 2.1.0
+    */
+    template <
+        typename ValueTypeCV,
+        typename ValueType = detail::uncvref_t<ValueTypeCV>,
+        detail::enable_if_t<not std::is_same<basic_json_t, ValueType>::value and
+                            detail::has_non_default_from_json<basic_json_t,
+                                    ValueType>::value, int> = 0 >
+    ValueType get() const noexcept(noexcept(
+                                       JSONSerializer<ValueTypeCV>::from_json(std::declval<const basic_json_t&>())))
+    {
+        static_assert(not std::is_reference<ValueTypeCV>::value,
+                      "get() cannot be used with reference types, you might want to use get_ref()");
+        return JSONSerializer<ValueTypeCV>::from_json(*this);
     }
 
     /*!
@@ -5528,7 +5837,7 @@ class basic_json
 
     /// @}
 
-
+  public:
     //////////////////////////////////////////
     // lexicographical comparison operators //
     //////////////////////////////////////////
@@ -5536,40 +5845,6 @@ class basic_json
     /// @name lexicographical comparison operators
     /// @{
 
-  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)];
-    }
-
-  public:
     /*!
     @brief comparison: equal
 
@@ -7782,28 +8057,30 @@ class basic_json
     @complexity Constant.
 
     @liveexample{The following code exemplifies `type_name()` for all JSON
-    types.,typename}
+    types.,type_name}
 
     @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";
+            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";
+            }
         }
     }
 
@@ -8155,6 +8432,11 @@ class basic_json
     class primitive_iterator_t
     {
       public:
+
+        difference_type get_value() const noexcept
+        {
+            return m_it;
+        }
         /// set iterator to a defined beginning
         void set_begin() noexcept
         {
@@ -8179,16 +8461,87 @@ class basic_json
             return (m_it == end_value);
         }
 
-        /// return reference to the value to change and compare
-        operator difference_type& () noexcept
+        friend constexpr bool operator==(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept
         {
-            return m_it;
+            return lhs.m_it == rhs.m_it;
         }
 
-        /// return value to compare
-        constexpr operator difference_type () const noexcept
+        friend constexpr bool operator!=(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept
         {
-            return m_it;
+            return !(lhs == rhs);
+        }
+
+        friend constexpr bool operator<(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept
+        {
+            return lhs.m_it < rhs.m_it;
+        }
+
+        friend constexpr bool operator<=(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept
+        {
+            return lhs.m_it <= rhs.m_it;
+        }
+
+        friend constexpr bool operator>(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept
+        {
+            return lhs.m_it > rhs.m_it;
+        }
+
+        friend constexpr bool operator>=(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept
+        {
+            return lhs.m_it >= rhs.m_it;
+        }
+
+        primitive_iterator_t operator+(difference_type i)
+        {
+            auto result = *this;
+            result += i;
+            return result;
+        }
+
+        friend constexpr difference_type operator-(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept
+        {
+            return lhs.m_it - rhs.m_it;
+        }
+
+        friend std::ostream& operator<<(std::ostream& os, primitive_iterator_t it)
+        {
+            return os << it.m_it;
+        }
+
+        primitive_iterator_t& operator++()
+        {
+            ++m_it;
+            return *this;
+        }
+
+        primitive_iterator_t& operator++(int)
+        {
+            m_it++;
+            return *this;
+        }
+
+        primitive_iterator_t& operator--()
+        {
+            --m_it;
+            return *this;
+        }
+
+        primitive_iterator_t& operator--(int)
+        {
+            m_it--;
+            return *this;
+        }
+
+        primitive_iterator_t& operator+=(difference_type n)
+        {
+            m_it += n;
+            return *this;
+        }
+
+        primitive_iterator_t& operator-=(difference_type n)
+        {
+            m_it -= n;
+            return *this;
         }
 
       private:
@@ -8893,7 +9246,7 @@ class basic_json
 
                 default:
                 {
-                    if (m_it.primitive_iterator == -n)
+                    if (m_it.primitive_iterator.get_value() == -n)
                     {
                         return *m_object;
                     }
@@ -10572,7 +10925,7 @@ basic_json_parser_66:
             for (; curptr < m_cursor; curptr++)
             {
                 // quickly skip tests if a digit
-                if (*curptr < '0' || *curptr > '9')
+                if (*curptr < '0' or* curptr > '9')
                 {
                     if (*curptr == '.')
                     {
@@ -11546,6 +11899,18 @@ basic_json_parser_66:
         }
 
       private:
+        friend bool operator==(json_pointer const& lhs,
+                               json_pointer const& rhs) noexcept
+        {
+            return lhs.reference_tokens == rhs.reference_tokens;
+        }
+
+        friend bool operator!=(json_pointer const& lhs,
+                               json_pointer const& rhs) noexcept
+        {
+            return !(lhs == rhs);
+        }
+
         /// the reference tokens
         std::vector<std::string> reference_tokens {};
     };
@@ -12206,7 +12571,6 @@ basic_json_parser_66:
     /// @}
 };
 
-
 /////////////
 // presets //
 /////////////
diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c
index f552a390..b1df2bf7 100644
--- a/src/json.hpp.re2c
+++ b/src/json.hpp.re2c
@@ -39,6 +39,7 @@ SOFTWARE.
 #include <cstdint> // int64_t, uint64_t
 #include <cstdlib> // strtod, strtof, strtold, strtoul
 #include <cstring> // strlen
+#include <forward_list> // forward_list
 #include <functional> // function, hash, less
 #include <initializer_list> // initializer_list
 #include <iomanip> // setw
@@ -107,37 +108,840 @@ SOFTWARE.
 namespace nlohmann
 {
 
-
 /*!
 @brief unnamed namespace with internal helper functions
+
+This namespace collects some functions that could not be defined inside the
+@ref basic_json class.
+
+@since version 2.1.0
+*/
+namespace detail
+{
+///////////////////////////
+// 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 basic_json::is_null(),
+@ref basic_json::is_object(), @ref basic_json::is_array(),
+@ref basic_json::is_string(), @ref basic_json::is_boolean(),
+@ref basic_json::is_number() (with @ref basic_json::is_number_integer(),
+@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()),
+@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and
+@ref basic_json::is_structured() rely on it.
+
+@note There are three enumeration entries (number_integer, number_unsigned, and
+number_float), because the library distinguishes these three types for numbers:
+@ref basic_json::number_unsigned_t is used for unsigned integers,
+@ref basic_json::number_integer_t is used for signed integers, and
+@ref basic_json::number_float_t is used for floating-point numbers or to
+approximate integers which do not fit in the limits of their respective type.
+
+@sa @ref basic_json::basic_json(const value_t value_type) -- create a JSON
+value with the default value for a given type
+
 @since version 1.0.0
 */
-namespace
+enum class value_t : uint8_t
 {
+    null,            ///< null value
+    object,          ///< object (unordered set of name/value pairs)
+    array,           ///< array (ordered collection of values)
+    string,          ///< string value
+    boolean,         ///< boolean value
+    number_integer,  ///< number value (signed integer)
+    number_unsigned, ///< number value (unsigned integer)
+    number_float,    ///< number value (floating-point)
+    discarded        ///< discarded by the the parser callback function
+};
+
+/*!
+@brief 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
+*/
+inline bool operator<(const value_t lhs, const value_t rhs) noexcept
+{
+    static constexpr std::array<uint8_t, 8> order = {{
+            0, // null
+            3, // object
+            4, // array
+            5, // string
+            1, // boolean
+            2, // integer
+            2, // unsigned
+            2, // float
+        }
+    };
+
+    // discarded values are not comparable
+    if (lhs == value_t::discarded or rhs == value_t::discarded)
+    {
+        return false;
+    }
+
+    return order[static_cast<std::size_t>(lhs)] <
+           order[static_cast<std::size_t>(rhs)];
+}
+
+
+/////////////
+// helpers //
+/////////////
+
+// alias templates to reduce boilerplate
+template<bool B, typename T = void>
+using enable_if_t = typename std::enable_if<B, T>::type;
+
+template<typename T>
+using uncvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
+
+// taken from http://stackoverflow.com/a/26936864/266378
+template<typename T>
+using is_unscoped_enum =
+    std::integral_constant<bool, std::is_convertible<T, int>::value and
+    std::is_enum<T>::value>;
+
+/*
+Implementation of two C++17 constructs: conjunction, negation. This is needed
+to avoid evaluating all the traits in a condition
+
+For example: not std::is_same<void, T>::value and has_value_type<T>::value
+will not compile when T = void (on MSVC at least). Whereas
+conjunction<negation<std::is_same<void, T>>, has_value_type<T>>::value will
+stop evaluating if negation<...>::value == false
+
+Please note that those constructs must be used with caution, since symbols can
+become very long quickly (which can slow down compilation and cause MSVC
+internal compiler errors). Only use it when you have to (see example ahead).
+*/
+template<class...> struct conjunction : std::true_type {};
+template<class B1> struct conjunction<B1> : B1 {};
+template<class B1, class... Bn>
+struct conjunction<B1, Bn...> : std::conditional<bool(B1::value), conjunction<Bn...>, B1>::type {};
+
+template<class B> struct negation : std::integral_constant < bool, !B::value > {};
+
+// dispatch utility (taken from ranges-v3)
+template<unsigned N> struct priority_tag : priority_tag < N - 1 > {};
+template<> struct priority_tag<0> {};
+
+
+//////////////////
+// constructors //
+//////////////////
+
+template<value_t> struct external_constructor;
+
+template<>
+struct external_constructor<value_t::boolean>
+{
+    template<typename BasicJsonType>
+    static void construct(BasicJsonType& j, typename BasicJsonType::boolean_t b) noexcept
+    {
+        j.m_type = value_t::boolean;
+        j.m_value = b;
+        j.assert_invariant();
+    }
+};
+
+template<>
+struct external_constructor<value_t::string>
+{
+    template<typename BasicJsonType>
+    static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s)
+    {
+        j.m_type = value_t::string;
+        j.m_value = s;
+        j.assert_invariant();
+    }
+};
+
+template<>
+struct external_constructor<value_t::number_float>
+{
+    template<typename BasicJsonType>
+    static void construct(BasicJsonType& j, typename BasicJsonType::number_float_t val) noexcept
+    {
+        // replace infinity and NAN by null
+        if (not std::isfinite(val))
+        {
+            j = BasicJsonType{};
+        }
+        else
+        {
+            j.m_type = value_t::number_float;
+            j.m_value = val;
+        }
+        j.assert_invariant();
+    }
+};
+
+template<>
+struct external_constructor<value_t::number_unsigned>
+{
+    template<typename BasicJsonType>
+    static void construct(BasicJsonType& j, typename BasicJsonType::number_unsigned_t val) noexcept
+    {
+        j.m_type = value_t::number_unsigned;
+        j.m_value = val;
+        j.assert_invariant();
+    }
+};
+
+template<>
+struct external_constructor<value_t::number_integer>
+{
+    template<typename BasicJsonType>
+    static void construct(BasicJsonType& j, typename BasicJsonType::number_integer_t val) noexcept
+    {
+        j.m_type = value_t::number_integer;
+        j.m_value = val;
+        j.assert_invariant();
+    }
+};
+
+template<>
+struct external_constructor<value_t::array>
+{
+    template<typename BasicJsonType>
+    static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr)
+    {
+        j.m_type = value_t::array;
+        j.m_value = arr;
+        j.assert_invariant();
+    }
+
+    template<typename BasicJsonType, typename CompatibleArrayType,
+             enable_if_t<not std::is_same<CompatibleArrayType,
+                                          typename BasicJsonType::array_t>::value,
+                         int> = 0>
+    static void construct(BasicJsonType& j, const CompatibleArrayType& arr)
+    {
+        using std::begin;
+        using std::end;
+        j.m_type = value_t::array;
+        j.m_value.array = j.template create<typename BasicJsonType::array_t>(begin(arr), end(arr));
+        j.assert_invariant();
+    }
+};
+
+template<>
+struct external_constructor<value_t::object>
+{
+    template<typename BasicJsonType>
+    static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj)
+    {
+        j.m_type = value_t::object;
+        j.m_value = obj;
+        j.assert_invariant();
+    }
+
+    template<typename BasicJsonType, typename CompatibleObjectType,
+             enable_if_t<not std::is_same<CompatibleObjectType,
+                                          typename BasicJsonType::object_t>::value,
+                         int> = 0>
+    static void construct(BasicJsonType& j, const CompatibleObjectType& obj)
+    {
+        using std::begin;
+        using std::end;
+
+        j.m_type = value_t::object;
+        j.m_value.object = j.template create<typename BasicJsonType::object_t>(begin(obj), end(obj));
+        j.assert_invariant();
+    }
+};
+
+
+////////////////////////
+// has_/is_ functions //
+////////////////////////
+
 /*!
 @brief Helper to determine whether there's a key_type for T.
 
-Thus helper is used to tell associative containers apart from other containers
+This helper is used to tell associative containers apart from other containers
 such as sequence containers. For instance, `std::map` passes the test as it
 contains a `mapped_type`, whereas `std::vector` fails the test.
 
 @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
-{
-  private:
-    template <typename U, typename = typename U::mapped_type>
-    static int detect(U&&);
+#define NLOHMANN_JSON_HAS_HELPER(type)                                        \
+    template<typename T> struct has_##type {                                  \
+    private:                                                                  \
+        template<typename U, typename = typename U::type>                     \
+        static int detect(U &&);                                              \
+        static void detect(...);                                              \
+    public:                                                                   \
+        static constexpr bool value =                                         \
+                std::is_integral<decltype(detect(std::declval<T>()))>::value; \
+    }
 
-    static void detect(...);
-  public:
-    static constexpr bool value =
-        std::is_integral<decltype(detect(std::declval<T>()))>::value;
+NLOHMANN_JSON_HAS_HELPER(mapped_type);
+NLOHMANN_JSON_HAS_HELPER(key_type);
+NLOHMANN_JSON_HAS_HELPER(value_type);
+NLOHMANN_JSON_HAS_HELPER(iterator);
+
+#undef NLOHMANN_JSON_HAS_HELPER
+
+
+template<bool B, class RealType, class CompatibleObjectType>
+struct is_compatible_object_type_impl : std::false_type {};
+
+template<class RealType, class CompatibleObjectType>
+struct is_compatible_object_type_impl<true, RealType, CompatibleObjectType>
+{
+    static constexpr auto value =
+        std::is_constructible<typename RealType::key_type,
+        typename CompatibleObjectType::key_type>::value and
+        std::is_constructible<typename RealType::mapped_type,
+        typename CompatibleObjectType::mapped_type>::value;
+};
+
+template<class BasicJsonType, class CompatibleObjectType>
+struct is_compatible_object_type
+{
+    static auto constexpr value = is_compatible_object_type_impl <
+                                  conjunction<negation<std::is_same<void, CompatibleObjectType>>,
+                                  has_mapped_type<CompatibleObjectType>,
+                                  has_key_type<CompatibleObjectType>>::value,
+                                  typename BasicJsonType::object_t, CompatibleObjectType >::value;
+};
+
+template<typename BasicJsonType, typename T>
+struct is_basic_json_nested_type
+{
+    static auto constexpr value = std::is_same<T, typename BasicJsonType::iterator>::value or
+                                  std::is_same<T, typename BasicJsonType::const_iterator>::value or
+                                  std::is_same<T, typename BasicJsonType::reverse_iterator>::value or
+                                  std::is_same<T, typename BasicJsonType::const_reverse_iterator>::value or
+                                  std::is_same<T, typename BasicJsonType::json_pointer>::value;
+};
+
+template<class BasicJsonType, class CompatibleArrayType>
+struct is_compatible_array_type
+{
+    static auto constexpr value =
+        conjunction<negation<std::is_same<void, CompatibleArrayType>>,
+        negation<is_compatible_object_type<
+        BasicJsonType, CompatibleArrayType>>,
+        negation<std::is_constructible<typename BasicJsonType::string_t,
+        CompatibleArrayType>>,
+        negation<is_basic_json_nested_type<BasicJsonType, CompatibleArrayType>>,
+        has_value_type<CompatibleArrayType>,
+        has_iterator<CompatibleArrayType>>::value;
+};
+
+template<bool, typename, typename>
+struct is_compatible_integer_type_impl : std::false_type {};
+
+template<typename RealIntegerType, typename CompatibleNumberIntegerType>
+struct is_compatible_integer_type_impl<true, RealIntegerType, CompatibleNumberIntegerType>
+{
+    // is there an assert somewhere on overflows?
+    using RealLimits = std::numeric_limits<RealIntegerType>;
+    using CompatibleLimits = std::numeric_limits<CompatibleNumberIntegerType>;
+
+    static constexpr auto value =
+        std::is_constructible<RealIntegerType,
+        CompatibleNumberIntegerType>::value and
+        CompatibleLimits::is_integer and
+        RealLimits::is_signed == CompatibleLimits::is_signed;
+};
+
+template<typename RealIntegerType, typename CompatibleNumberIntegerType>
+struct is_compatible_integer_type
+{
+    static constexpr auto value =
+        is_compatible_integer_type_impl <
+        std::is_integral<CompatibleNumberIntegerType>::value and
+        not std::is_same<bool, CompatibleNumberIntegerType>::value,
+        RealIntegerType, CompatibleNumberIntegerType > ::value;
+};
+
+
+// trait checking if JSONSerializer<T>::from_json(json const&, udt&) exists
+template<typename BasicJsonType, typename T>
+struct has_from_json
+{
+  private:
+    // also check the return type of from_json
+    template<typename U, typename = enable_if_t<std::is_same<void, decltype(uncvref_t<U>::from_json(
+                 std::declval<BasicJsonType>(), std::declval<T&>()))>::value>>
+    static int detect(U&&);
+    static void detect(...);
+
+  public:
+    static constexpr bool value = std::is_integral<decltype(
+                                      detect(std::declval<typename BasicJsonType::template json_serializer<T, void>>()))>::value;
+};
+
+// This trait checks if JSONSerializer<T>::from_json(json const&) exists
+// this overload is used for non-default-constructible user-defined-types
+template<typename BasicJsonType, typename T>
+struct has_non_default_from_json
+{
+  private:
+    template <
+        typename U,
+        typename = enable_if_t<std::is_same<
+                                   T, decltype(uncvref_t<U>::from_json(std::declval<BasicJsonType>()))>::value >>
+    static int detect(U&&);
+    static void detect(...);
+
+  public:
+    static constexpr bool value = std::is_integral<decltype(detect(
+                                      std::declval<typename BasicJsonType::template json_serializer<T, void>>()))>::value;
+};
+
+// This trait checks if BasicJsonType::json_serializer<T>::to_json exists
+template<typename BasicJsonType, typename T>
+struct has_to_json
+{
+  private:
+    template<typename U, typename = decltype(uncvref_t<U>::to_json(
+                 std::declval<BasicJsonType&>(), std::declval<T>()))>
+    static int detect(U&&);
+    static void detect(...);
+
+  public:
+    static constexpr bool value = std::is_integral<decltype(detect(
+                                      std::declval<typename BasicJsonType::template json_serializer<T, void>>()))>::value;
+};
+
+
+/////////////
+// to_json //
+/////////////
+
+template<typename BasicJsonType>
+void to_json(BasicJsonType& j, typename BasicJsonType::boolean_t b) noexcept
+{
+    external_constructor<value_t::boolean>::construct(j, b);
+}
+
+template<typename BasicJsonType, typename CompatibleString,
+         enable_if_t<std::is_constructible<typename BasicJsonType::string_t,
+                     CompatibleString>::value, int> = 0>
+void to_json(BasicJsonType& j, const CompatibleString& s)
+{
+    external_constructor<value_t::string>::construct(j, s);
+}
+
+template<typename BasicJsonType, typename FloatType,
+         enable_if_t<std::is_floating_point<FloatType>::value, int> = 0>
+void to_json(BasicJsonType& j, FloatType val) noexcept
+{
+    external_constructor<value_t::number_float>::construct(j, static_cast<typename BasicJsonType::number_float_t>(val));
+}
+
+template <
+    typename BasicJsonType, typename CompatibleNumberUnsignedType,
+    enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_unsigned_t,
+                CompatibleNumberUnsignedType>::value, int> = 0 >
+void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept
+{
+    external_constructor<value_t::number_unsigned>::construct(j, static_cast<typename BasicJsonType::number_unsigned_t>(val));
+}
+
+template <
+    typename BasicJsonType, typename CompatibleNumberIntegerType,
+    enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_integer_t,
+                CompatibleNumberIntegerType>::value, int> = 0 >
+void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept
+{
+    external_constructor<value_t::number_integer>::construct(j, static_cast<typename BasicJsonType::number_integer_t>(val));
+}
+
+template<typename BasicJsonType, typename UnscopedEnumType,
+         enable_if_t<is_unscoped_enum<UnscopedEnumType>::value, int> = 0>
+void to_json(BasicJsonType& j, UnscopedEnumType e) noexcept
+{
+    external_constructor<value_t::number_integer>::construct(j, e);
+}
+
+template <
+    typename BasicJsonType, typename CompatibleArrayType,
+    enable_if_t <
+        is_compatible_array_type<BasicJsonType, CompatibleArrayType>::value or
+        std::is_same<typename BasicJsonType::array_t, CompatibleArrayType>::value,
+        int > = 0 >
+void to_json(BasicJsonType& j, const  CompatibleArrayType& arr)
+{
+    external_constructor<value_t::array>::construct(j, arr);
+}
+
+template <
+    typename BasicJsonType, typename CompatibleObjectType,
+    enable_if_t<is_compatible_object_type<BasicJsonType, CompatibleObjectType>::value,
+                int> = 0 >
+void to_json(BasicJsonType& j, const  CompatibleObjectType& arr)
+{
+    external_constructor<value_t::object>::construct(j, arr);
+}
+
+
+///////////////
+// from_json //
+///////////////
+
+// overloads for basic_json template parameters
+template<typename BasicJsonType, typename ArithmeticType,
+         enable_if_t<std::is_arithmetic<ArithmeticType>::value and
+                     not std::is_same<ArithmeticType,
+                                      typename BasicJsonType::boolean_t>::value,
+                     int> = 0>
+void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val)
+{
+    switch (static_cast<value_t>(j))
+    {
+        case value_t::number_unsigned:
+        {
+            val = static_cast<ArithmeticType>(
+                      *j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>());
+            break;
+        }
+        case value_t::number_integer:
+        {
+            val = static_cast<ArithmeticType>(
+                      *j.template get_ptr<const typename BasicJsonType::number_integer_t*>());
+            break;
+        }
+        case value_t::number_float:
+        {
+            val = static_cast<ArithmeticType>(
+                      *j.template get_ptr<const typename BasicJsonType::number_float_t*>());
+            break;
+        }
+        default:
+        {
+            JSON_THROW(
+                std::domain_error("type must be number, but is " + j.type_name()));
+        }
+    }
+}
+
+template<typename BasicJsonType>
+void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b)
+{
+    if (not j.is_boolean())
+    {
+        JSON_THROW(std::domain_error("type must be boolean, but is " + j.type_name()));
+    }
+    b = *j.template get_ptr<const typename BasicJsonType::boolean_t*>();
+}
+
+template<typename BasicJsonType>
+void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s)
+{
+    if (not j.is_string())
+    {
+        JSON_THROW(std::domain_error("type must be string, but is " + j.type_name()));
+    }
+    s = *j.template get_ptr<const typename BasicJsonType::string_t*>();
+}
+
+template<typename BasicJsonType>
+void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val)
+{
+    get_arithmetic_value(j, val);
+}
+
+template<typename BasicJsonType>
+void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val)
+{
+    get_arithmetic_value(j, val);
+}
+
+template<typename BasicJsonType>
+void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val)
+{
+    get_arithmetic_value(j, val);
+}
+
+template<typename BasicJsonType, typename UnscopedEnumType,
+         enable_if_t<is_unscoped_enum<UnscopedEnumType>::value, int> = 0>
+void from_json(const BasicJsonType& j, UnscopedEnumType& e)
+{
+    typename std::underlying_type<UnscopedEnumType>::type val = e;
+    get_arithmetic_value(j, val);
+    e = static_cast<UnscopedEnumType>(val);
+}
+
+template<typename BasicJsonType>
+void from_json(const BasicJsonType& j, typename BasicJsonType::array_t& arr)
+{
+    if (not j.is_array())
+    {
+        JSON_THROW(std::domain_error("type must be array, but is " + j.type_name()));
+    }
+    arr = *j.template get_ptr<const typename BasicJsonType::array_t*>();
+}
+
+// forward_list doesn't have an insert method
+template<typename BasicJsonType, typename T, typename Allocator>
+void from_json(const BasicJsonType& j, std::forward_list<T, Allocator>& l)
+{
+    // do not perform the check when user wants to retrieve jsons
+    // (except when it's null.. ?)
+    if (j.is_null())
+    {
+        JSON_THROW(std::domain_error("type must be array, but is " + j.type_name()));
+    }
+    if (not std::is_same<T, BasicJsonType>::value)
+    {
+        if (not j.is_array())
+        {
+            JSON_THROW(std::domain_error("type must be array, but is " + j.type_name()));
+        }
+    }
+    for (auto it = j.rbegin(), end = j.rend(); it != end; ++it)
+    {
+        l.push_front(it->template get<T>());
+    }
+}
+
+template<typename BasicJsonType, typename CompatibleArrayType>
+void from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<0>)
+{
+    using std::begin;
+    using std::end;
+
+    std::transform(j.begin(), j.end(),
+                   std::inserter(arr, end(arr)), [](const BasicJsonType & i)
+    {
+        // get<BasicJsonType>() returns *this, this won't call a from_json
+        // method when value_type is BasicJsonType
+        return i.template get<typename CompatibleArrayType::value_type>();
+    });
+}
+
+template<typename BasicJsonType, typename CompatibleArrayType>
+auto from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<1>)
+-> decltype(
+    arr.reserve(std::declval<typename CompatibleArrayType::size_type>()),
+    void())
+{
+    using std::begin;
+    using std::end;
+
+    arr.reserve(j.size());
+    std::transform(
+        j.begin(), j.end(), std::inserter(arr, end(arr)), [](const BasicJsonType & i)
+    {
+        // get<BasicJsonType>() returns *this, this won't call a from_json
+        // method when value_type is BasicJsonType
+        return i.template get<typename CompatibleArrayType::value_type>();
+    });
+}
+
+template<typename BasicJsonType, typename CompatibleArrayType,
+         enable_if_t<is_compatible_array_type<BasicJsonType, CompatibleArrayType>::value and
+                     not std::is_same<typename BasicJsonType::array_t, CompatibleArrayType>::value, int> = 0>
+void from_json(const BasicJsonType& j, CompatibleArrayType& arr)
+{
+    if (j.is_null())
+    {
+        JSON_THROW(std::domain_error("type must be array, but is " + j.type_name()));
+    }
+
+    // when T == BasicJsonType, do not check if value_t is correct
+    if (not std::is_same<typename CompatibleArrayType::value_type, BasicJsonType>::value)
+    {
+        if (not j.is_array())
+        {
+            JSON_THROW(std::domain_error("type must be array, but is " + j.type_name()));
+        }
+    }
+    from_json_array_impl(j, arr, priority_tag<1> {});
+}
+
+template<typename BasicJsonType, typename CompatibleObjectType,
+         enable_if_t<is_compatible_object_type<BasicJsonType, CompatibleObjectType>::value, int> = 0>
+void from_json(const BasicJsonType& j, CompatibleObjectType& obj)
+{
+    if (not j.is_object())
+    {
+        JSON_THROW(std::domain_error("type must be object, but is " + j.type_name()));
+    }
+
+    auto inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>();
+    using std::begin;
+    using std::end;
+    // we could avoid the assignment, but this might require a for loop, which
+    // might be less efficient than the container constructor for some
+    // containers (would it?)
+    obj = CompatibleObjectType(begin(*inner_object), end(*inner_object));
+}
+
+// overload for arithmetic types, not chosen for basic_json template arguments
+// (BooleanType, etc..); note: Is it really necessary to provide explicit
+// overloads for boolean_t etc. in case of a custom BooleanType which is not
+// an arithmetic type?
+template<typename BasicJsonType, typename ArithmeticType,
+         enable_if_t <
+             std::is_arithmetic<ArithmeticType>::value and
+             not std::is_same<ArithmeticType, typename BasicJsonType::number_unsigned_t>::value and
+             not std::is_same<ArithmeticType, typename BasicJsonType::number_integer_t>::value and
+             not std::is_same<ArithmeticType, typename BasicJsonType::number_float_t>::value and
+             not std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value,
+             int> = 0>
+void from_json(const BasicJsonType& j, ArithmeticType& val)
+{
+    switch (static_cast<value_t>(j))
+    {
+        case value_t::number_unsigned:
+        {
+            val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>());
+            break;
+        }
+        case value_t::number_integer:
+        {
+            val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t*>());
+            break;
+        }
+        case value_t::number_float:
+        {
+            val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t*>());
+            break;
+        }
+        case value_t::boolean:
+        {
+            val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::boolean_t*>());
+            break;
+        }
+        default:
+        {
+            JSON_THROW(std::domain_error("type must be number, but is " + j.type_name()));
+        }
+    }
+}
+
+struct to_json_fn
+{
+  private:
+    template<typename BasicJsonType, typename T>
+    auto call(BasicJsonType& j, T&& val, priority_tag<1>) const noexcept(noexcept(to_json(j, std::forward<T>(val))))
+    -> decltype(to_json(j, std::forward<T>(val)), void())
+    {
+        return to_json(j, std::forward<T>(val));
+    }
+
+    template<typename BasicJsonType, typename T>
+    void call(BasicJsonType&, T&&, priority_tag<0>) const noexcept
+    {
+        static_assert(sizeof(BasicJsonType) == 0,
+                      "could not find to_json() method in T's namespace");
+    }
+
+  public:
+    template<typename BasicJsonType, typename T>
+    void operator()(BasicJsonType& j, T&& val) const
+    noexcept(noexcept(std::declval<to_json_fn>().call(j, std::forward<T>(val), priority_tag<1> {})))
+    {
+        return call(j, std::forward<T>(val), priority_tag<1> {});
+    }
+};
+
+struct from_json_fn
+{
+  private:
+    template<typename BasicJsonType, typename T>
+    auto call(const BasicJsonType& j, T& val, priority_tag<1>) const
+    noexcept(noexcept(from_json(j, val)))
+    -> decltype(from_json(j, val), void())
+    {
+        return from_json(j, val);
+    }
+
+    template<typename BasicJsonType, typename T>
+    void call(const BasicJsonType&, T&, priority_tag<0>) const noexcept
+    {
+        static_assert(sizeof(BasicJsonType) == 0,
+                      "could not find from_json() method in T's namespace");
+    }
+
+  public:
+    template<typename BasicJsonType, typename T>
+    void operator()(const BasicJsonType& j, T& val) const
+    noexcept(noexcept(std::declval<from_json_fn>().call(j, val, priority_tag<1> {})))
+    {
+        return call(j, val, priority_tag<1> {});
+    }
+};
+
+// taken from ranges-v3
+template<typename T>
+struct static_const
+{
+    static constexpr T value{};
+};
+
+template<typename T>
+constexpr T static_const<T>::value;
+} // namespace detail
+
+
+/// namespace to hold default `to_json` / `from_json` functions
+namespace
+{
+constexpr const auto& to_json = detail::static_const<detail::to_json_fn>::value;
+constexpr const auto& from_json = detail::static_const<detail::from_json_fn>::value;
+}
+
+
+/*!
+@brief default JSONSerializer template argument
+
+This serializer ignores the template arguments and uses ADL
+([argument-dependent lookup](http://en.cppreference.com/w/cpp/language/adl))
+for serialization.
+*/
+template<typename = void, typename = void>
+struct adl_serializer
+{
+    /*!
+    @brief convert a JSON value to any value type
+
+    This function is usually called by the `get()` function of the
+    @ref basic_json class (either explicit or via conversion operators).
+
+    @param[in] j         JSON value to read from
+    @param[in, out] val  value to write to
+    */
+    template<typename BasicJsonType, typename ValueType>
+    static void from_json(BasicJsonType&& j, ValueType& val) noexcept(
+        noexcept(::nlohmann::from_json(std::forward<BasicJsonType>(j), val)))
+    {
+        ::nlohmann::from_json(std::forward<BasicJsonType>(j), val);
+    }
+
+    /*!
+    @brief convert any value type to a JSON value
+
+    This function is usually called by the constructors of the @ref basic_json
+    class.
+
+    @param[in, out] j  JSON value to write to
+    @param[in] val     value to read from
+    */
+    template<typename BasicJsonType, typename ValueType>
+    static void to_json(BasicJsonType& j, ValueType&& val) noexcept(
+        noexcept(::nlohmann::to_json(j, std::forward<ValueType>(val))))
+    {
+        ::nlohmann::to_json(j, std::forward<ValueType>(val));
+    }
 };
 
-} // namespace
 
 /*!
 @brief a class to store JSON values
@@ -158,6 +962,8 @@ default; will be used in @ref number_integer_t)
 default; will be used in @ref number_float_t)
 @tparam AllocatorType type of the allocator to use (`std::allocator` by
 default)
+@tparam JSONSerializer the serializer to resolve internal calls to `to_json()`
+and `from_json()` (@ref adl_serializer by default)
 
 @requirement The class satisfies the following concept requirements:
 - Basic
@@ -226,21 +1032,26 @@ template <
     class NumberIntegerType = std::int64_t,
     class NumberUnsignedType = std::uint64_t,
     class NumberFloatType = double,
-    template<typename U> class AllocatorType = std::allocator
+    template<typename U> class AllocatorType = std::allocator,
+    template<typename T, typename SFINAE = void> class JSONSerializer = adl_serializer
     >
 class basic_json
 {
   private:
+    template<detail::value_t> friend struct detail::external_constructor;
     /// workaround type for MSVC
     using basic_json_t = basic_json<ObjectType, ArrayType, StringType,
           BooleanType, NumberIntegerType, NumberUnsignedType, NumberFloatType,
-          AllocatorType>;
+          AllocatorType, JSONSerializer>;
 
   public:
+    using value_t = detail::value_t;
     // forward declarations
     template<typename U> class iter_impl;
     template<typename Base> class json_reverse_iterator;
     class json_pointer;
+    template<typename T, typename SFINAE>
+    using json_serializer = JSONSerializer<T, SFINAE>;
 
     /////////////////////
     // container types //
@@ -787,47 +1598,6 @@ class basic_json
 
     /// @}
 
-
-    ///////////////////////////
-    // 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
-    };
-
-
   private:
 
     /// helper for exception-safe object creation
@@ -1120,18 +1890,6 @@ class basic_json
     @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)
@@ -1165,473 +1923,69 @@ class basic_json
     }
 
     /*!
-    @brief create an object (explicit)
+    @brief create a JSON value
 
-    Create an object JSON value with a given content.
+    This is a "catch all" constructor for all compatible JSON types; that is,
+    types for which a `to_json()` method exsits. The constructor forwards the
+    parameter @a val to that method (to `json_serializer<U>::to_json` method
+    with `U = uncvref_t<CompatibleType>`, to be exact).
 
-    @param[in] val  a value for the object
+    Template type @a CompatibleType includes, but is not limited to, the
+    following types:
+    - **arrays**: @ref array_t and all kinds of compatible containers such as
+      `std::vector`, `std::deque`, `std::list`, `std::forward_list`,
+      `std::array`, `std::set`, `std::unordered_set`, `std::multiset`, and
+      `unordered_multiset` with a `value_type` from which a @ref basic_json
+      value can be constructed.
+    - **objects**: @ref object_t and all kinds of compatible associative
+      containers such as `std::map`, `std::unordered_map`, `std::multimap`,
+      and `std::unordered_multimap` with a `key_type` compatible to
+      @ref string_t and a `value_type` from which a @ref basic_json value can
+      be constructed.
+    - **strings**: @ref string_t, string literals, and all compatible string
+      containers can be used.
+    - **numbers**: @ref number_integer_t, @ref number_unsigned_t,
+      @ref number_float_t, and all convertible number types such as `int`,
+      `size_t`, `int64_t`, `float` or `double` can be used.
+    - **boolean**: @ref boolean_t / `bool` can be used.
 
-    @complexity Linear in the size of the passed @a val.
+    See the examples below.
 
-    @throw std::bad_alloc if allocation for object value fails
+    @tparam CompatibleType a type such that:
+    - @a CompatibleType is not derived from `std::istream`,
+    - @a CompatibleType is not @ref basic_json (to avoid hijacking copy/move
+         constructors),
+    - @a CompatibleType is not a @ref basic_json nested type (e.g.,
+         @ref json_pointer, @ref iterator, etc ...)
+    - @ref @ref json_serializer<U> has a
+         `to_json(basic_json_t&, CompatibleType&&)` method
 
-    @liveexample{The following code shows the constructor with an @ref
-    object_t parameter.,basic_json__object_t}
+    @tparam U = `uncvref_t<CompatibleType>`
 
-    @sa @ref basic_json(const CompatibleObjectType&) -- create an object value
-    from a compatible STL container
+    @param[in] val the value to be forwarded
 
-    @since version 1.0.0
-    */
-    basic_json(const object_t& val)
-        : m_type(value_t::object), m_value(val)
-    {
-        assert_invariant();
-    }
+    @complexity Usually linear in the size of the passed @a val, also
+                depending on the implementation of the called `to_json()`
+                method.
 
-    /*!
-    @brief create an object (implicit)
-
-    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
+    @throw what `json_serializer<U>::to_json()` throws
 
     @liveexample{The following code shows the constructor with several
-    compatible object type parameters.,basic_json__CompatibleObjectType}
+    compatible types.,basic_json__CompatibleType}
 
-    @sa @ref basic_json(const object_t&) -- create an object value
-
-    @since version 1.0.0
+    @since version 2.1.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
-
-    @complexity Linear in the size of the passed @a val.
-
-    @throw std::bad_alloc if allocation for array value fails
-
-    @liveexample{The following code shows the constructor with an @ref array_t
-    parameter.,basic_json__array_t}
-
-    @sa @ref basic_json(const CompatibleArrayType&) -- create an array value
-    from a compatible STL containers
-
-    @since version 1.0.0
-    */
-    basic_json(const array_t& val)
-        : m_type(value_t::array), m_value(val)
-    {
-        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.
-
-    @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.
-
-    @param[in] val  a value for the array
-
-    @complexity Linear in the size of the passed @a val.
-
-    @throw std::bad_alloc if allocation for array value fails
-
-    @liveexample{The following code shows the constructor with several
-    compatible array type parameters.,basic_json__CompatibleArrayType}
-
-    @sa @ref basic_json(const array_t&) -- create an array value
-
-    @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();
-    }
-
-    /*!
-    @brief create a string (explicit)
-
-    Create an string JSON value with a given content.
-
-    @param[in] val  a value for the string
-
-    @complexity Linear in the size of the passed @a val.
-
-    @throw std::bad_alloc if allocation for string value fails
-
-    @liveexample{The following code shows the constructor with an @ref
-    string_t parameter.,basic_json__string_t}
-
-    @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
-
-    @since version 1.0.0
-    */
-    basic_json(const string_t& val)
-        : m_type(value_t::string), m_value(val)
-    {
-        assert_invariant();
-    }
-
-    /*!
-    @brief create a string (explicit)
-
-    Create a string JSON value with a given content.
-
-    @param[in] val  a literal value for the string
-
-    @complexity Linear in the size of the passed @a val.
-
-    @throw std::bad_alloc if allocation for string value fails
-
-    @liveexample{The following code shows the constructor with string literal
-    parameter.,basic_json__string_t_value_type}
-
-    @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
-
-    @since version 1.0.0
-    */
-    basic_json(const typename string_t::value_type* val)
-        : basic_json(string_t(val))
-    {
-        assert_invariant();
-    }
-
-    /*!
-    @brief create a string (implicit)
-
-    Create a string JSON value with a given content.
-
-    @param[in] val  a value for the string
-
-    @tparam CompatibleStringType an string type which is compatible to @ref
-    string_t, for instance `std::string`.
-
-    @complexity Linear in the size of the passed @a val.
-
-    @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();
-    }
-
-    /*!
-    @brief create a boolean (explicit)
-
-    Creates a JSON boolean type from a given value.
-
-    @param[in] val  a boolean value to store
-
-    @complexity Constant.
-
-    @liveexample{The example below demonstrates boolean
-    values.,basic_json__boolean_t}
-
-    @since version 1.0.0
-    */
-    basic_json(boolean_t val) noexcept
-        : m_type(value_t::boolean), m_value(val)
-    {
-        assert_invariant();
-    }
-
-    /*!
-    @brief create an integer number (explicit)
-
-    Create an integer number JSON value with a given content.
-
-    @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  an integer to create a JSON number from
-
-    @complexity Constant.
-
-    @liveexample{The example below shows the construction of an integer
-    number value.,basic_json__number_integer_t}
-
-    @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
-    */
-    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 create an integer number from an enum type (explicit)
-
-    Create an integer number JSON value with a given content.
-
-    @param[in] val  an integer to create a JSON number from
-
-    @note This constructor allows to pass enums directly to a constructor. As
-    C++ has no way of specifying the type of an anonymous enum explicitly, we
-    can only rely on the fact that such values implicitly convert to int. As
-    int may already be the same type of number_integer_t, we may need to
-    switch off the constructor @ref basic_json(const number_integer_t).
-
-    @complexity Constant.
-
-    @liveexample{The example below shows the construction of an integer
-    number value from an anonymous enum.,basic_json__const_int}
-
-    @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))
+    template<typename CompatibleType, typename U = detail::uncvref_t<CompatibleType>,
+             detail::enable_if_t<not std::is_base_of<std::istream, U>::value and
+                                 not std::is_same<U, basic_json_t>::value and
+                                 not detail::is_basic_json_nested_type<
+                                     basic_json_t, U>::value and
+                                 detail::has_to_json<basic_json, U>::value,
+                                 int> = 0>
+    basic_json(CompatibleType && val) noexcept(noexcept(JSONSerializer<U>::to_json(
+                std::declval<basic_json_t&>(), std::forward<CompatibleType>(val))))
     {
+        JSONSerializer<U>::to_json(*this, std::forward<CompatibleType>(val));
         assert_invariant();
     }
 
@@ -2655,142 +3009,6 @@ class basic_json
     // value access //
     //////////////////
 
-    /// get an object (explicit)
-    template<class T, typename std::enable_if<
-                 std::is_convertible<typename object_t::key_type, typename T::key_type>::value and
-                 std::is_convertible<basic_json_t, typename T::mapped_type>::value, int>::type = 0>
-    T get_impl(T* /*unused*/) const
-    {
-        if (is_object())
-        {
-            return T(m_value.object->begin(), m_value.object->end());
-        }
-
-        JSON_THROW(std::domain_error("type must be object, but is " + type_name()));
-    }
-
-    /// get an object (explicit)
-    object_t get_impl(object_t* /*unused*/) const
-    {
-        if (is_object())
-        {
-            return *(m_value.object);
-        }
-
-        JSON_THROW(std::domain_error("type must be object, but is " + type_name()));
-    }
-
-    /// get an array (explicit)
-    template<class T, typename std::enable_if<
-                 std::is_convertible<basic_json_t, typename T::value_type>::value and
-                 not std::is_same<basic_json_t, typename T::value_type>::value and
-                 not std::is_arithmetic<T>::value and
-                 not std::is_convertible<std::string, T>::value and
-                 not has_mapped_type<T>::value, int>::type = 0>
-    T get_impl(T* /*unused*/) const
-    {
-        if (is_array())
-        {
-            T to_vector;
-            std::transform(m_value.array->begin(), m_value.array->end(),
-                           std::inserter(to_vector, to_vector.end()), [](basic_json i)
-            {
-                return i.get<typename T::value_type>();
-            });
-            return to_vector;
-        }
-
-        JSON_THROW(std::domain_error("type must be array, but is " + type_name()));
-    }
-
-    /// get an array (explicit)
-    template<class T, typename std::enable_if<
-                 std::is_convertible<basic_json_t, T>::value and
-                 not std::is_same<basic_json_t, T>::value, int>::type = 0>
-    std::vector<T> get_impl(std::vector<T>* /*unused*/) const
-    {
-        if (is_array())
-        {
-            std::vector<T> to_vector;
-            to_vector.reserve(m_value.array->size());
-            std::transform(m_value.array->begin(), m_value.array->end(),
-                           std::inserter(to_vector, to_vector.end()), [](basic_json i)
-            {
-                return i.get<T>();
-            });
-            return to_vector;
-        }
-
-        JSON_THROW(std::domain_error("type must be array, but is " + type_name()));
-    }
-
-    /// get an array (explicit)
-    template<class T, typename std::enable_if<
-                 std::is_same<basic_json, typename T::value_type>::value and
-                 not has_mapped_type<T>::value, int>::type = 0>
-    T get_impl(T* /*unused*/) const
-    {
-        if (is_array())
-        {
-            return T(m_value.array->begin(), m_value.array->end());
-        }
-
-        JSON_THROW(std::domain_error("type must be array, but is " + type_name()));
-    }
-
-    /// get an array (explicit)
-    array_t get_impl(array_t* /*unused*/) const
-    {
-        if (is_array())
-        {
-            return *(m_value.array);
-        }
-
-        JSON_THROW(std::domain_error("type must be array, but is " + type_name()));
-    }
-
-    /// get a string (explicit)
-    template<typename T, typename std::enable_if<
-                 std::is_convertible<string_t, T>::value, int>::type = 0>
-    T get_impl(T* /*unused*/) const
-    {
-        if (is_string())
-        {
-            return *m_value.string;
-        }
-
-        JSON_THROW(std::domain_error("type must be string, but is " + type_name()));
-    }
-
-    /// get a number (explicit)
-    template<typename T, typename std::enable_if<
-                 std::is_arithmetic<T>::value, int>::type = 0>
-    T get_impl(T* /*unused*/) const
-    {
-        switch (m_type)
-        {
-            case value_t::number_integer:
-            {
-                return static_cast<T>(m_value.number_integer);
-            }
-
-            case value_t::number_unsigned:
-            {
-                return static_cast<T>(m_value.number_unsigned);
-            }
-
-            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
     {
@@ -2918,26 +3136,63 @@ class basic_json
     }
 
   public:
-
     /// @name value access
     /// Direct access to the stored value of a JSON value.
     /// @{
 
+    /*!
+    @brief get special-case overload
+
+    This overloads avoids a lot of template boilerplate, it can be seen as the
+    identity method
+
+    @tparam BasicJsonType == @ref basic_json
+
+    @return a copy of *this
+
+    @complexity Constant.
+
+    @since version 2.1.0
+    */
+    template <
+        typename BasicJsonType,
+        detail::enable_if_t<std::is_same<typename std::remove_const<BasicJsonType>::type,
+                                         basic_json_t>::value,
+                            int> = 0 >
+    basic_json get() const
+    {
+        return *this;
+    }
+
     /*!
     @brief get a value (explicit)
 
-    Explicit type conversion between the JSON value and a compatible value.
+    Explicit type conversion between the JSON value and a compatible value
+    which is [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible)
+    and [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible).
+    The value is converted by calling the @ref json_serializer<ValueType>
+    `from_json()` method.
 
-    @tparam ValueType non-pointer type compatible to the JSON value, for
-    instance `int` for JSON integer numbers, `bool` for JSON booleans, or
-    `std::vector` types for JSON arrays
+    The function is equivalent to executing
+    @code {.cpp}
+    ValueType ret;
+    JSONSerializer<ValueType>::from_json(*this, ret);
+    return ret;
+    @endcode
 
-    @return copy of the JSON value, converted to type @a ValueType
+    This overloads is chosen if:
+    - @a ValueType is not @ref basic_json,
+    - @ref json_serializer<ValueType> has a `from_json()` method of the form
+      `void from_json(const @ref basic_json&, ValueType&)`, and
+    - @ref json_serializer<ValueType> does not have a `from_json()` method of
+      the form `ValueType from_json(const @ref basic_json&)`
 
-    @throw std::domain_error in case passed type @a ValueType is incompatible
-    to JSON; example: `"type must be object, but is null"`
+    @tparam ValueTypeCV the provided value type
+    @tparam ValueType the returned value type
 
-    @complexity Linear in the size of the JSON value.
+    @return copy of the JSON value, converted to @a ValueType
+
+    @throw what @ref json_serializer<ValueType> `from_json()` method throws
 
     @liveexample{The example below shows several conversions from JSON values
     to other types. There a few things to note: (1) Floating-point numbers can
@@ -2946,21 +3201,75 @@ class basic_json
     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
+    @since version 2.1.0
     */
-    template<typename ValueType, typename std::enable_if<
-                 not std::is_pointer<ValueType>::value, int>::type = 0>
-    ValueType get() const
+    template <
+        typename ValueTypeCV,
+        typename ValueType = detail::uncvref_t<ValueTypeCV>,
+        detail::enable_if_t <
+            not std::is_same<basic_json_t, ValueType>::value and
+            detail::has_from_json<basic_json_t, ValueType>::value and
+            not detail::has_non_default_from_json<basic_json_t, ValueType>::value,
+            int > = 0 >
+    ValueType get() const noexcept(noexcept(
+                                       JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>(), std::declval<ValueType&>())))
     {
-        return get_impl(static_cast<ValueType*>(nullptr));
+        // we cannot static_assert on ValueTypeCV being non-const, because
+        // there is support for get<const basic_json_t>(), which is why we
+        // still need the uncvref
+        static_assert(not std::is_reference<ValueTypeCV>::value,
+                      "get() cannot be used with reference types, you might want to use get_ref()");
+        static_assert(std::is_default_constructible<ValueType>::value,
+                      "types must be DefaultConstructible when used with get()");
+
+        ValueType ret;
+        JSONSerializer<ValueType>::from_json(*this, ret);
+        return ret;
+    }
+
+    /*!
+    @brief get a value (explicit); special case
+
+    Explicit type conversion between the JSON value and a compatible value
+    which is **not** [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible)
+    and **not** [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible).
+    The value is converted by calling the @ref json_serializer<ValueType>
+    `from_json()` method.
+
+    The function is equivalent to executing
+    @code {.cpp}
+    return JSONSerializer<ValueTypeCV>::from_json(*this);
+    @endcode
+
+    This overloads is chosen if:
+    - @a ValueType is not @ref basic_json and
+    - @ref json_serializer<ValueType> has a `from_json()` method of the form
+      `ValueType from_json(const @ref basic_json&)`
+
+    @note If @ref json_serializer<ValueType> has both overloads of
+    `from_json()`, this one is chosen.
+
+    @tparam ValueTypeCV the provided value type
+    @tparam ValueType the returned value type
+
+    @return copy of the JSON value, converted to @a ValueType
+
+    @throw what @ref json_serializer<ValueType> `from_json()` method throws
+
+    @since version 2.1.0
+    */
+    template <
+        typename ValueTypeCV,
+        typename ValueType = detail::uncvref_t<ValueTypeCV>,
+        detail::enable_if_t<not std::is_same<basic_json_t, ValueType>::value and
+                            detail::has_non_default_from_json<basic_json_t,
+                                    ValueType>::value, int> = 0 >
+    ValueType get() const noexcept(noexcept(
+                                       JSONSerializer<ValueTypeCV>::from_json(std::declval<const basic_json_t&>())))
+    {
+        static_assert(not std::is_reference<ValueTypeCV>::value,
+                      "get() cannot be used with reference types, you might want to use get_ref()");
+        return JSONSerializer<ValueTypeCV>::from_json(*this);
     }
 
     /*!
@@ -5528,7 +5837,7 @@ class basic_json
 
     /// @}
 
-
+  public:
     //////////////////////////////////////////
     // lexicographical comparison operators //
     //////////////////////////////////////////
@@ -5536,40 +5845,6 @@ class basic_json
     /// @name lexicographical comparison operators
     /// @{
 
-  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)];
-    }
-
-  public:
     /*!
     @brief comparison: equal
 
@@ -7782,28 +8057,30 @@ class basic_json
     @complexity Constant.
 
     @liveexample{The following code exemplifies `type_name()` for all JSON
-    types.,typename}
+    types.,type_name}
 
     @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";
+            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";
+            }
         }
     }
 
@@ -8155,6 +8432,11 @@ class basic_json
     class primitive_iterator_t
     {
       public:
+
+        difference_type get_value() const noexcept
+        {
+            return m_it;
+        }
         /// set iterator to a defined beginning
         void set_begin() noexcept
         {
@@ -8179,16 +8461,87 @@ class basic_json
             return (m_it == end_value);
         }
 
-        /// return reference to the value to change and compare
-        operator difference_type& () noexcept
+        friend constexpr bool operator==(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept
         {
-            return m_it;
+            return lhs.m_it == rhs.m_it;
         }
 
-        /// return value to compare
-        constexpr operator difference_type () const noexcept
+        friend constexpr bool operator!=(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept
         {
-            return m_it;
+            return !(lhs == rhs);
+        }
+
+        friend constexpr bool operator<(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept
+        {
+            return lhs.m_it < rhs.m_it;
+        }
+
+        friend constexpr bool operator<=(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept
+        {
+            return lhs.m_it <= rhs.m_it;
+        }
+
+        friend constexpr bool operator>(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept
+        {
+            return lhs.m_it > rhs.m_it;
+        }
+
+        friend constexpr bool operator>=(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept
+        {
+            return lhs.m_it >= rhs.m_it;
+        }
+
+        primitive_iterator_t operator+(difference_type i)
+        {
+            auto result = *this;
+            result += i;
+            return result;
+        }
+
+        friend constexpr difference_type operator-(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept
+        {
+            return lhs.m_it - rhs.m_it;
+        }
+
+        friend std::ostream& operator<<(std::ostream& os, primitive_iterator_t it)
+        {
+            return os << it.m_it;
+        }
+
+        primitive_iterator_t& operator++()
+        {
+            ++m_it;
+            return *this;
+        }
+
+        primitive_iterator_t& operator++(int)
+        {
+            m_it++;
+            return *this;
+        }
+
+        primitive_iterator_t& operator--()
+        {
+            --m_it;
+            return *this;
+        }
+
+        primitive_iterator_t& operator--(int)
+        {
+            m_it--;
+            return *this;
+        }
+
+        primitive_iterator_t& operator+=(difference_type n)
+        {
+            m_it += n;
+            return *this;
+        }
+
+        primitive_iterator_t& operator-=(difference_type n)
+        {
+            m_it -= n;
+            return *this;
         }
 
       private:
@@ -8893,7 +9246,7 @@ class basic_json
 
                 default:
                 {
-                    if (m_it.primitive_iterator == -n)
+                    if (m_it.primitive_iterator.get_value() == -n)
                     {
                         return *m_object;
                     }
@@ -9722,7 +10075,7 @@ class basic_json
             for (; curptr < m_cursor; curptr++)
             {
                 // quickly skip tests if a digit
-                if (*curptr < '0' || *curptr > '9')
+                if (*curptr < '0' or* curptr > '9')
                 {
                     if (*curptr == '.')
                     {
@@ -10696,6 +11049,18 @@ class basic_json
         }
 
       private:
+        friend bool operator==(json_pointer const& lhs,
+                               json_pointer const& rhs) noexcept
+        {
+            return lhs.reference_tokens == rhs.reference_tokens;
+        }
+
+        friend bool operator!=(json_pointer const& lhs,
+                               json_pointer const& rhs) noexcept
+        {
+            return !(lhs == rhs);
+        }
+
         /// the reference tokens
         std::vector<std::string> reference_tokens {};
     };
@@ -11356,7 +11721,6 @@ class basic_json
     /// @}
 };
 
-
 /////////////
 // presets //
 /////////////
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 62213ad3..0ceb6bf6 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -29,12 +29,14 @@ add_executable(${JSON_UNITTEST_TARGET_NAME}
     "src/unit-meta.cpp"
     "src/unit-modifiers.cpp"
     "src/unit-msgpack.cpp"
+    "src/unit-noexcept.cpp"
     "src/unit-pointer_access.cpp"
     "src/unit-readme.cpp"
     "src/unit-reference_access.cpp"
     "src/unit-regression.cpp"
     "src/unit-serialization.cpp"
     "src/unit-testsuites.cpp"
+    "src/unit-udt.cpp"
     "src/unit-unicode.cpp"
 )
 
diff --git a/test/src/unit-class_const_iterator.cpp b/test/src/unit-class_const_iterator.cpp
index 13ce7c3f..dba96e1f 100644
--- a/test/src/unit-class_const_iterator.cpp
+++ b/test/src/unit-class_const_iterator.cpp
@@ -91,7 +91,7 @@ TEST_CASE("const_iterator class")
                 json j(json::value_t::null);
                 json::const_iterator it(&j);
                 it.set_begin();
-                CHECK(it == j.cbegin());
+                CHECK((it == j.cbegin()));
             }
 
             SECTION("object")
@@ -99,7 +99,7 @@ TEST_CASE("const_iterator class")
                 json j(json::value_t::object);
                 json::const_iterator it(&j);
                 it.set_begin();
-                CHECK(it == j.cbegin());
+                CHECK((it == j.cbegin()));
             }
 
             SECTION("array")
@@ -107,7 +107,7 @@ TEST_CASE("const_iterator class")
                 json j(json::value_t::array);
                 json::const_iterator it(&j);
                 it.set_begin();
-                CHECK(it == j.cbegin());
+                CHECK((it == j.cbegin()));
             }
         }
 
@@ -118,7 +118,7 @@ TEST_CASE("const_iterator class")
                 json j(json::value_t::null);
                 json::const_iterator it(&j);
                 it.set_end();
-                CHECK(it == j.cend());
+                CHECK((it == j.cend()));
             }
 
             SECTION("object")
@@ -126,7 +126,7 @@ TEST_CASE("const_iterator class")
                 json j(json::value_t::object);
                 json::const_iterator it(&j);
                 it.set_end();
-                CHECK(it == j.cend());
+                CHECK((it == j.cend()));
             }
 
             SECTION("array")
@@ -134,7 +134,7 @@ TEST_CASE("const_iterator class")
                 json j(json::value_t::array);
                 json::const_iterator it(&j);
                 it.set_end();
-                CHECK(it == j.cend());
+                CHECK((it == j.cend()));
             }
         }
     }
@@ -220,48 +220,48 @@ TEST_CASE("const_iterator class")
             {
                 json j(json::value_t::null);
                 json::const_iterator it = j.cbegin();
-                CHECK(it.m_it.primitive_iterator == 1);
+                CHECK((it.m_it.primitive_iterator.m_it == 1));
                 it++;
-                CHECK((it.m_it.primitive_iterator != 0 and it.m_it.primitive_iterator != 1));
+                CHECK((it.m_it.primitive_iterator.m_it != 0 and it.m_it.primitive_iterator.m_it != 1));
             }
 
             SECTION("number")
             {
                 json j(17);
                 json::const_iterator it = j.cbegin();
-                CHECK(it.m_it.primitive_iterator == 0);
+                CHECK((it.m_it.primitive_iterator.m_it == 0));
                 it++;
-                CHECK(it.m_it.primitive_iterator == 1);
+                CHECK((it.m_it.primitive_iterator.m_it == 1));
                 it++;
-                CHECK((it.m_it.primitive_iterator != 0 and it.m_it.primitive_iterator != 1));
+                CHECK((it.m_it.primitive_iterator.m_it != 0 and it.m_it.primitive_iterator.m_it != 1));
             }
 
             SECTION("object")
             {
                 json j({{"foo", "bar"}});
                 json::const_iterator it = j.cbegin();
-                CHECK(it.m_it.object_iterator == it.m_object->m_value.object->begin());
+                CHECK((it.m_it.object_iterator == it.m_object->m_value.object->begin()));
                 it++;
-                CHECK(it.m_it.object_iterator == it.m_object->m_value.object->end());
+                CHECK((it.m_it.object_iterator == it.m_object->m_value.object->end()));
             }
 
             SECTION("array")
             {
                 json j({1, 2, 3, 4});
                 json::const_iterator it = j.cbegin();
-                CHECK(it.m_it.array_iterator == it.m_object->m_value.array->begin());
+                CHECK((it.m_it.array_iterator == it.m_object->m_value.array->begin()));
                 it++;
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin());
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end());
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin()));
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end()));
                 it++;
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin());
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end());
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin()));
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end()));
                 it++;
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin());
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end());
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin()));
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end()));
                 it++;
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin());
-                CHECK(it.m_it.array_iterator == it.m_object->m_value.array->end());
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin()));
+                CHECK((it.m_it.array_iterator == it.m_object->m_value.array->end()));
             }
         }
 
@@ -271,48 +271,48 @@ TEST_CASE("const_iterator class")
             {
                 json j(json::value_t::null);
                 json::const_iterator it = j.cbegin();
-                CHECK(it.m_it.primitive_iterator == 1);
+                CHECK((it.m_it.primitive_iterator.m_it == 1));
                 ++it;
-                CHECK((it.m_it.primitive_iterator != 0 and it.m_it.primitive_iterator != 1));
+                CHECK((it.m_it.primitive_iterator.m_it != 0 and it.m_it.primitive_iterator.m_it != 1));
             }
 
             SECTION("number")
             {
                 json j(17);
                 json::const_iterator it = j.cbegin();
-                CHECK(it.m_it.primitive_iterator == 0);
+                CHECK((it.m_it.primitive_iterator.m_it == 0));
                 ++it;
-                CHECK(it.m_it.primitive_iterator == 1);
+                CHECK((it.m_it.primitive_iterator.m_it == 1));
                 ++it;
-                CHECK((it.m_it.primitive_iterator != 0 and it.m_it.primitive_iterator != 1));
+                CHECK((it.m_it.primitive_iterator.m_it != 0 and it.m_it.primitive_iterator.m_it != 1));
             }
 
             SECTION("object")
             {
                 json j({{"foo", "bar"}});
                 json::const_iterator it = j.cbegin();
-                CHECK(it.m_it.object_iterator == it.m_object->m_value.object->begin());
+                CHECK((it.m_it.object_iterator == it.m_object->m_value.object->begin()));
                 ++it;
-                CHECK(it.m_it.object_iterator == it.m_object->m_value.object->end());
+                CHECK((it.m_it.object_iterator == it.m_object->m_value.object->end()));
             }
 
             SECTION("array")
             {
                 json j({1, 2, 3, 4});
                 json::const_iterator it = j.cbegin();
-                CHECK(it.m_it.array_iterator == it.m_object->m_value.array->begin());
+                CHECK((it.m_it.array_iterator == it.m_object->m_value.array->begin()));
                 ++it;
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin());
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end());
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin()));
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end()));
                 ++it;
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin());
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end());
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin()));
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end()));
                 ++it;
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin());
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end());
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin()));
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end()));
                 ++it;
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin());
-                CHECK(it.m_it.array_iterator == it.m_object->m_value.array->end());
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin()));
+                CHECK((it.m_it.array_iterator == it.m_object->m_value.array->end()));
             }
         }
 
@@ -322,46 +322,46 @@ TEST_CASE("const_iterator class")
             {
                 json j(json::value_t::null);
                 json::const_iterator it = j.cend();
-                CHECK(it.m_it.primitive_iterator == 1);
+                CHECK((it.m_it.primitive_iterator.m_it == 1));
             }
 
             SECTION("number")
             {
                 json j(17);
                 json::const_iterator it = j.cend();
-                CHECK(it.m_it.primitive_iterator == 1);
+                CHECK((it.m_it.primitive_iterator.m_it == 1));
                 it--;
-                CHECK(it.m_it.primitive_iterator == 0);
+                CHECK((it.m_it.primitive_iterator.m_it == 0));
                 it--;
-                CHECK((it.m_it.primitive_iterator != 0 and it.m_it.primitive_iterator != 1));
+                CHECK((it.m_it.primitive_iterator.m_it != 0 and it.m_it.primitive_iterator.m_it != 1));
             }
 
             SECTION("object")
             {
                 json j({{"foo", "bar"}});
                 json::const_iterator it = j.cend();
-                CHECK(it.m_it.object_iterator == it.m_object->m_value.object->end());
+                CHECK((it.m_it.object_iterator == it.m_object->m_value.object->end()));
                 it--;
-                CHECK(it.m_it.object_iterator == it.m_object->m_value.object->begin());
+                CHECK((it.m_it.object_iterator == it.m_object->m_value.object->begin()));
             }
 
             SECTION("array")
             {
                 json j({1, 2, 3, 4});
                 json::const_iterator it = j.cend();
-                CHECK(it.m_it.array_iterator == it.m_object->m_value.array->end());
+                CHECK((it.m_it.array_iterator == it.m_object->m_value.array->end()));
                 it--;
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin());
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end());
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin()));
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end()));
                 it--;
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin());
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end());
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin()));
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end()));
                 it--;
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin());
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end());
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin()));
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end()));
                 it--;
-                CHECK(it.m_it.array_iterator == it.m_object->m_value.array->begin());
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end());
+                CHECK((it.m_it.array_iterator == it.m_object->m_value.array->begin()));
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end()));
             }
         }
 
@@ -371,46 +371,46 @@ TEST_CASE("const_iterator class")
             {
                 json j(json::value_t::null);
                 json::const_iterator it = j.cend();
-                CHECK(it.m_it.primitive_iterator == 1);
+                CHECK((it.m_it.primitive_iterator.m_it == 1));
             }
 
             SECTION("number")
             {
                 json j(17);
                 json::const_iterator it = j.cend();
-                CHECK(it.m_it.primitive_iterator == 1);
+                CHECK((it.m_it.primitive_iterator.m_it == 1));
                 --it;
-                CHECK(it.m_it.primitive_iterator == 0);
+                CHECK((it.m_it.primitive_iterator.m_it == 0));
                 --it;
-                CHECK((it.m_it.primitive_iterator != 0 and it.m_it.primitive_iterator != 1));
+                CHECK((it.m_it.primitive_iterator.m_it != 0 and it.m_it.primitive_iterator.m_it != 1));
             }
 
             SECTION("object")
             {
                 json j({{"foo", "bar"}});
                 json::const_iterator it = j.cend();
-                CHECK(it.m_it.object_iterator == it.m_object->m_value.object->end());
+                CHECK((it.m_it.object_iterator == it.m_object->m_value.object->end()));
                 --it;
-                CHECK(it.m_it.object_iterator == it.m_object->m_value.object->begin());
+                CHECK((it.m_it.object_iterator == it.m_object->m_value.object->begin()));
             }
 
             SECTION("array")
             {
                 json j({1, 2, 3, 4});
                 json::const_iterator it = j.cend();
-                CHECK(it.m_it.array_iterator == it.m_object->m_value.array->end());
+                CHECK((it.m_it.array_iterator == it.m_object->m_value.array->end()));
                 --it;
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin());
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end());
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin()));
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end()));
                 --it;
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin());
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end());
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin()));
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end()));
                 --it;
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin());
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end());
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin()));
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end()));
                 --it;
-                CHECK(it.m_it.array_iterator == it.m_object->m_value.array->begin());
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end());
+                CHECK((it.m_it.array_iterator == it.m_object->m_value.array->begin()));
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end()));
             }
         }
     }
diff --git a/test/src/unit-class_iterator.cpp b/test/src/unit-class_iterator.cpp
index 640bc816..16833cd9 100644
--- a/test/src/unit-class_iterator.cpp
+++ b/test/src/unit-class_iterator.cpp
@@ -75,7 +75,7 @@ TEST_CASE("iterator class")
                 json j(json::value_t::null);
                 json::iterator it(&j);
                 it.set_begin();
-                CHECK(it == j.begin());
+                CHECK((it == j.begin()));
             }
 
             SECTION("object")
@@ -83,7 +83,7 @@ TEST_CASE("iterator class")
                 json j(json::value_t::object);
                 json::iterator it(&j);
                 it.set_begin();
-                CHECK(it == j.begin());
+                CHECK((it == j.begin()));
             }
 
             SECTION("array")
@@ -91,7 +91,7 @@ TEST_CASE("iterator class")
                 json j(json::value_t::array);
                 json::iterator it(&j);
                 it.set_begin();
-                CHECK(it == j.begin());
+                CHECK((it == j.begin()));
             }
         }
 
@@ -102,7 +102,7 @@ TEST_CASE("iterator class")
                 json j(json::value_t::null);
                 json::iterator it(&j);
                 it.set_end();
-                CHECK(it == j.end());
+                CHECK((it == j.end()));
             }
 
             SECTION("object")
@@ -110,7 +110,7 @@ TEST_CASE("iterator class")
                 json j(json::value_t::object);
                 json::iterator it(&j);
                 it.set_end();
-                CHECK(it == j.end());
+                CHECK((it == j.end()));
             }
 
             SECTION("array")
@@ -118,7 +118,7 @@ TEST_CASE("iterator class")
                 json j(json::value_t::array);
                 json::iterator it(&j);
                 it.set_end();
-                CHECK(it == j.end());
+                CHECK((it == j.end()));
             }
         }
     }
@@ -204,48 +204,48 @@ TEST_CASE("iterator class")
             {
                 json j(json::value_t::null);
                 json::iterator it = j.begin();
-                CHECK(it.m_it.primitive_iterator == 1);
+                CHECK((it.m_it.primitive_iterator.m_it == 1));
                 it++;
-                CHECK((it.m_it.primitive_iterator != 0 and it.m_it.primitive_iterator != 1));
+                CHECK((it.m_it.primitive_iterator.m_it != 0 and it.m_it.primitive_iterator.m_it != 1));
             }
 
             SECTION("number")
             {
                 json j(17);
                 json::iterator it = j.begin();
-                CHECK(it.m_it.primitive_iterator == 0);
+                CHECK((it.m_it.primitive_iterator.m_it == 0));
                 it++;
-                CHECK(it.m_it.primitive_iterator == 1);
+                CHECK((it.m_it.primitive_iterator.m_it == 1));
                 it++;
-                CHECK((it.m_it.primitive_iterator != 0 and it.m_it.primitive_iterator != 1));
+                CHECK((it.m_it.primitive_iterator.m_it != 0 and it.m_it.primitive_iterator.m_it != 1));
             }
 
             SECTION("object")
             {
                 json j({{"foo", "bar"}});
                 json::iterator it = j.begin();
-                CHECK(it.m_it.object_iterator == it.m_object->m_value.object->begin());
+                CHECK((it.m_it.object_iterator == it.m_object->m_value.object->begin()));
                 it++;
-                CHECK(it.m_it.object_iterator == it.m_object->m_value.object->end());
+                CHECK((it.m_it.object_iterator == it.m_object->m_value.object->end()));
             }
 
             SECTION("array")
             {
                 json j({1, 2, 3, 4});
                 json::iterator it = j.begin();
-                CHECK(it.m_it.array_iterator == it.m_object->m_value.array->begin());
+                CHECK((it.m_it.array_iterator == it.m_object->m_value.array->begin()));
                 it++;
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin());
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end());
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin()));
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end()));
                 it++;
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin());
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end());
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin()));
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end()));
                 it++;
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin());
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end());
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin()));
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end()));
                 it++;
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin());
-                CHECK(it.m_it.array_iterator == it.m_object->m_value.array->end());
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin()));
+                CHECK((it.m_it.array_iterator == it.m_object->m_value.array->end()));
             }
         }
 
@@ -255,48 +255,48 @@ TEST_CASE("iterator class")
             {
                 json j(json::value_t::null);
                 json::iterator it = j.begin();
-                CHECK(it.m_it.primitive_iterator == 1);
+                CHECK((it.m_it.primitive_iterator.m_it == 1));
                 ++it;
-                CHECK((it.m_it.primitive_iterator != 0 and it.m_it.primitive_iterator != 1));
+                CHECK((it.m_it.primitive_iterator.m_it != 0 and it.m_it.primitive_iterator.m_it != 1));
             }
 
             SECTION("number")
             {
                 json j(17);
                 json::iterator it = j.begin();
-                CHECK(it.m_it.primitive_iterator == 0);
+                CHECK((it.m_it.primitive_iterator.m_it == 0));
                 ++it;
-                CHECK(it.m_it.primitive_iterator == 1);
+                CHECK((it.m_it.primitive_iterator.m_it == 1));
                 ++it;
-                CHECK((it.m_it.primitive_iterator != 0 and it.m_it.primitive_iterator != 1));
+                CHECK((it.m_it.primitive_iterator.m_it != 0 and it.m_it.primitive_iterator.m_it != 1));
             }
 
             SECTION("object")
             {
                 json j({{"foo", "bar"}});
                 json::iterator it = j.begin();
-                CHECK(it.m_it.object_iterator == it.m_object->m_value.object->begin());
+                CHECK((it.m_it.object_iterator == it.m_object->m_value.object->begin()));
                 ++it;
-                CHECK(it.m_it.object_iterator == it.m_object->m_value.object->end());
+                CHECK((it.m_it.object_iterator == it.m_object->m_value.object->end()));
             }
 
             SECTION("array")
             {
                 json j({1, 2, 3, 4});
                 json::iterator it = j.begin();
-                CHECK(it.m_it.array_iterator == it.m_object->m_value.array->begin());
+                CHECK((it.m_it.array_iterator == it.m_object->m_value.array->begin()));
                 ++it;
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin());
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end());
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin()));
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end()));
                 ++it;
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin());
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end());
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin()));
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end()));
                 ++it;
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin());
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end());
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin()));
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end()));
                 ++it;
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin());
-                CHECK(it.m_it.array_iterator == it.m_object->m_value.array->end());
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin()));
+                CHECK((it.m_it.array_iterator == it.m_object->m_value.array->end()));
             }
         }
 
@@ -306,46 +306,46 @@ TEST_CASE("iterator class")
             {
                 json j(json::value_t::null);
                 json::iterator it = j.end();
-                CHECK(it.m_it.primitive_iterator == 1);
+                CHECK((it.m_it.primitive_iterator.m_it == 1));
             }
 
             SECTION("number")
             {
                 json j(17);
                 json::iterator it = j.end();
-                CHECK(it.m_it.primitive_iterator == 1);
+                CHECK((it.m_it.primitive_iterator.m_it == 1));
                 it--;
-                CHECK(it.m_it.primitive_iterator == 0);
+                CHECK((it.m_it.primitive_iterator.m_it == 0));
                 it--;
-                CHECK((it.m_it.primitive_iterator != 0 and it.m_it.primitive_iterator != 1));
+                CHECK((it.m_it.primitive_iterator.m_it != 0 and it.m_it.primitive_iterator.m_it != 1));
             }
 
             SECTION("object")
             {
                 json j({{"foo", "bar"}});
                 json::iterator it = j.end();
-                CHECK(it.m_it.object_iterator == it.m_object->m_value.object->end());
+                CHECK((it.m_it.object_iterator == it.m_object->m_value.object->end()));
                 it--;
-                CHECK(it.m_it.object_iterator == it.m_object->m_value.object->begin());
+                CHECK((it.m_it.object_iterator == it.m_object->m_value.object->begin()));
             }
 
             SECTION("array")
             {
                 json j({1, 2, 3, 4});
                 json::iterator it = j.end();
-                CHECK(it.m_it.array_iterator == it.m_object->m_value.array->end());
+                CHECK((it.m_it.array_iterator == it.m_object->m_value.array->end()));
                 it--;
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin());
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end());
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin()));
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end()));
                 it--;
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin());
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end());
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin()));
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end()));
                 it--;
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin());
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end());
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin()));
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end()));
                 it--;
-                CHECK(it.m_it.array_iterator == it.m_object->m_value.array->begin());
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end());
+                CHECK((it.m_it.array_iterator == it.m_object->m_value.array->begin()));
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end()));
             }
         }
 
@@ -355,46 +355,46 @@ TEST_CASE("iterator class")
             {
                 json j(json::value_t::null);
                 json::iterator it = j.end();
-                CHECK(it.m_it.primitive_iterator == 1);
+                CHECK((it.m_it.primitive_iterator.m_it == 1));
             }
 
             SECTION("number")
             {
                 json j(17);
                 json::iterator it = j.end();
-                CHECK(it.m_it.primitive_iterator == 1);
+                CHECK((it.m_it.primitive_iterator.m_it == 1));
                 --it;
-                CHECK(it.m_it.primitive_iterator == 0);
+                CHECK((it.m_it.primitive_iterator.m_it == 0));
                 --it;
-                CHECK((it.m_it.primitive_iterator != 0 and it.m_it.primitive_iterator != 1));
+                CHECK((it.m_it.primitive_iterator.m_it != 0 and it.m_it.primitive_iterator.m_it != 1));
             }
 
             SECTION("object")
             {
                 json j({{"foo", "bar"}});
                 json::iterator it = j.end();
-                CHECK(it.m_it.object_iterator == it.m_object->m_value.object->end());
+                CHECK((it.m_it.object_iterator == it.m_object->m_value.object->end()));
                 --it;
-                CHECK(it.m_it.object_iterator == it.m_object->m_value.object->begin());
+                CHECK((it.m_it.object_iterator == it.m_object->m_value.object->begin()));
             }
 
             SECTION("array")
             {
                 json j({1, 2, 3, 4});
                 json::iterator it = j.end();
-                CHECK(it.m_it.array_iterator == it.m_object->m_value.array->end());
+                CHECK((it.m_it.array_iterator == it.m_object->m_value.array->end()));
                 --it;
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin());
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end());
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin()));
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end()));
                 --it;
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin());
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end());
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin()));
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end()));
                 --it;
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->begin());
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end());
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->begin()));
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end()));
                 --it;
-                CHECK(it.m_it.array_iterator == it.m_object->m_value.array->begin());
-                CHECK(it.m_it.array_iterator != it.m_object->m_value.array->end());
+                CHECK((it.m_it.array_iterator == it.m_object->m_value.array->begin()));
+                CHECK((it.m_it.array_iterator != it.m_object->m_value.array->end()));
             }
         }
     }
diff --git a/test/src/unit-class_lexer.cpp b/test/src/unit-class_lexer.cpp
index 33ea610a..71e75df4 100644
--- a/test/src/unit-class_lexer.cpp
+++ b/test/src/unit-class_lexer.cpp
@@ -38,86 +38,86 @@ TEST_CASE("lexer class")
     {
         SECTION("structural characters")
         {
-            CHECK(json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("["),
-                              1).scan() == json::lexer::token_type::begin_array);
-            CHECK(json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("]"),
-                              1).scan() == json::lexer::token_type::end_array);
-            CHECK(json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("{"),
-                              1).scan() == json::lexer::token_type::begin_object);
-            CHECK(json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("}"),
-                              1).scan() == json::lexer::token_type::end_object);
-            CHECK(json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>(","),
-                              1).scan() == json::lexer::token_type::value_separator);
-            CHECK(json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>(":"),
-                              1).scan() == json::lexer::token_type::name_separator);
+            CHECK((json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("["),
+                               1).scan() == json::lexer::token_type::begin_array));
+            CHECK((json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("]"),
+                               1).scan() == json::lexer::token_type::end_array));
+            CHECK((json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("{"),
+                               1).scan() == json::lexer::token_type::begin_object));
+            CHECK((json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("}"),
+                               1).scan() == json::lexer::token_type::end_object));
+            CHECK((json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>(","),
+                               1).scan() == json::lexer::token_type::value_separator));
+            CHECK((json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>(":"),
+                               1).scan() == json::lexer::token_type::name_separator));
         }
 
         SECTION("literal names")
         {
-            CHECK(json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("null"),
-                              4).scan() == json::lexer::token_type::literal_null);
-            CHECK(json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("true"),
-                              4).scan() == json::lexer::token_type::literal_true);
-            CHECK(json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("false"),
-                              5).scan() == json::lexer::token_type::literal_false);
+            CHECK((json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("null"),
+                               4).scan() == json::lexer::token_type::literal_null));
+            CHECK((json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("true"),
+                               4).scan() == json::lexer::token_type::literal_true));
+            CHECK((json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("false"),
+                               5).scan() == json::lexer::token_type::literal_false));
         }
 
         SECTION("numbers")
         {
-            CHECK(json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("0"),
-                              1).scan() == json::lexer::token_type::value_number);
-            CHECK(json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("1"),
-                              1).scan() == json::lexer::token_type::value_number);
-            CHECK(json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("2"),
-                              1).scan() == json::lexer::token_type::value_number);
-            CHECK(json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("3"),
-                              1).scan() == json::lexer::token_type::value_number);
-            CHECK(json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("4"),
-                              1).scan() == json::lexer::token_type::value_number);
-            CHECK(json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("5"),
-                              1).scan() == json::lexer::token_type::value_number);
-            CHECK(json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("6"),
-                              1).scan() == json::lexer::token_type::value_number);
-            CHECK(json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("7"),
-                              1).scan() == json::lexer::token_type::value_number);
-            CHECK(json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("8"),
-                              1).scan() == json::lexer::token_type::value_number);
-            CHECK(json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("9"),
-                              1).scan() == json::lexer::token_type::value_number);
+            CHECK((json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("0"),
+                               1).scan() == json::lexer::token_type::value_number));
+            CHECK((json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("1"),
+                               1).scan() == json::lexer::token_type::value_number));
+            CHECK((json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("2"),
+                               1).scan() == json::lexer::token_type::value_number));
+            CHECK((json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("3"),
+                               1).scan() == json::lexer::token_type::value_number));
+            CHECK((json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("4"),
+                               1).scan() == json::lexer::token_type::value_number));
+            CHECK((json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("5"),
+                               1).scan() == json::lexer::token_type::value_number));
+            CHECK((json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("6"),
+                               1).scan() == json::lexer::token_type::value_number));
+            CHECK((json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("7"),
+                               1).scan() == json::lexer::token_type::value_number));
+            CHECK((json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("8"),
+                               1).scan() == json::lexer::token_type::value_number));
+            CHECK((json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("9"),
+                               1).scan() == json::lexer::token_type::value_number));
         }
 
         SECTION("whitespace")
         {
             // result is end_of_input, because not token is following
-            CHECK(json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>(" "),
-                              1).scan() == json::lexer::token_type::end_of_input);
-            CHECK(json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("\t"),
-                              1).scan() == json::lexer::token_type::end_of_input);
-            CHECK(json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("\n"),
-                              1).scan() == json::lexer::token_type::end_of_input);
-            CHECK(json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("\r"),
-                              1).scan() == json::lexer::token_type::end_of_input);
-            CHECK(json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>(" \t\n\r\n\t "),
-                              7).scan() == json::lexer::token_type::end_of_input);
+            CHECK((json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>(" "),
+                               1).scan() == json::lexer::token_type::end_of_input));
+            CHECK((json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("\t"),
+                               1).scan() == json::lexer::token_type::end_of_input));
+            CHECK((json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("\n"),
+                               1).scan() == json::lexer::token_type::end_of_input));
+            CHECK((json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>("\r"),
+                               1).scan() == json::lexer::token_type::end_of_input));
+            CHECK((json::lexer(reinterpret_cast<const json::lexer::lexer_char_t*>(" \t\n\r\n\t "),
+                               7).scan() == json::lexer::token_type::end_of_input));
         }
     }
 
     SECTION("token_type_name")
     {
-        CHECK(json::lexer::token_type_name(json::lexer::token_type::uninitialized) == "<uninitialized>");
-        CHECK(json::lexer::token_type_name(json::lexer::token_type::literal_true) == "true literal");
-        CHECK(json::lexer::token_type_name(json::lexer::token_type::literal_false) == "false literal");
-        CHECK(json::lexer::token_type_name(json::lexer::token_type::literal_null) == "null literal");
-        CHECK(json::lexer::token_type_name(json::lexer::token_type::value_string) == "string literal");
-        CHECK(json::lexer::token_type_name(json::lexer::token_type::value_number) == "number literal");
-        CHECK(json::lexer::token_type_name(json::lexer::token_type::begin_array) == "'['");
-        CHECK(json::lexer::token_type_name(json::lexer::token_type::begin_object) == "'{'");
-        CHECK(json::lexer::token_type_name(json::lexer::token_type::end_array) == "']'");
-        CHECK(json::lexer::token_type_name(json::lexer::token_type::end_object) == "'}'");
-        CHECK(json::lexer::token_type_name(json::lexer::token_type::name_separator) == "':'");
-        CHECK(json::lexer::token_type_name(json::lexer::token_type::value_separator) == "','");
-        CHECK(json::lexer::token_type_name(json::lexer::token_type::parse_error) == "<parse error>");
-        CHECK(json::lexer::token_type_name(json::lexer::token_type::end_of_input) == "end of input");
+        CHECK((json::lexer::token_type_name(json::lexer::token_type::uninitialized) == "<uninitialized>"));
+        CHECK((json::lexer::token_type_name(json::lexer::token_type::literal_true) == "true literal"));
+        CHECK((json::lexer::token_type_name(json::lexer::token_type::literal_false) == "false literal"));
+        CHECK((json::lexer::token_type_name(json::lexer::token_type::literal_null) == "null literal"));
+        CHECK((json::lexer::token_type_name(json::lexer::token_type::value_string) == "string literal"));
+        CHECK((json::lexer::token_type_name(json::lexer::token_type::value_number) == "number literal"));
+        CHECK((json::lexer::token_type_name(json::lexer::token_type::begin_array) == "'['"));
+        CHECK((json::lexer::token_type_name(json::lexer::token_type::begin_object) == "'{'"));
+        CHECK((json::lexer::token_type_name(json::lexer::token_type::end_array) == "']'"));
+        CHECK((json::lexer::token_type_name(json::lexer::token_type::end_object) == "'}'"));
+        CHECK((json::lexer::token_type_name(json::lexer::token_type::name_separator) == "':'"));
+        CHECK((json::lexer::token_type_name(json::lexer::token_type::value_separator) == "','"));
+        CHECK((json::lexer::token_type_name(json::lexer::token_type::parse_error) == "<parse error>"));
+        CHECK((json::lexer::token_type_name(json::lexer::token_type::end_of_input) == "end of input"));
     }
 
     SECTION("parse errors on first character")
@@ -150,7 +150,7 @@ TEST_CASE("lexer class")
                 case ('8'):
                 case ('9'):
                 {
-                    CHECK(res != json::lexer::token_type::parse_error);
+                    CHECK((res != json::lexer::token_type::parse_error));
                     break;
                 }
 
@@ -160,14 +160,14 @@ TEST_CASE("lexer class")
                 case ('\n'):
                 case ('\r'):
                 {
-                    CHECK(res == json::lexer::token_type::end_of_input);
+                    CHECK((res == json::lexer::token_type::end_of_input));
                     break;
                 }
 
                 // anything else is not expected
                 default:
                 {
-                    CHECK(res == json::lexer::token_type::parse_error);
+                    CHECK((res == json::lexer::token_type::parse_error));
                     break;
                 }
             }
diff --git a/test/src/unit-conversions.cpp b/test/src/unit-conversions.cpp
index b82127bb..994f882d 100644
--- a/test/src/unit-conversions.cpp
+++ b/test/src/unit-conversions.cpp
@@ -160,12 +160,30 @@ TEST_CASE("value conversion")
         {
             std::forward_list<json> a = j.get<std::forward_list<json>>();
             CHECK(json(a) == j);
+
+            CHECK_THROWS_AS(json(json::value_t::null).get<std::forward_list<json>>(), std::logic_error);
+            CHECK_THROWS_WITH(json(json::value_t::null).get<std::forward_list<json>>(),
+                              "type must be array, but is null");
         }
 
         SECTION("std::vector<json>")
         {
             std::vector<json> a = j.get<std::vector<json>>();
             CHECK(json(a) == j);
+
+            CHECK_THROWS_AS(json(json::value_t::null).get<std::vector<json>>(), std::logic_error);
+            CHECK_THROWS_WITH(json(json::value_t::null).get<std::vector<json>>(),
+                              "type must be array, but is null");
+
+#if not defined(JSON_NOEXCEPTION)
+            SECTION("reserve is called on containers that supports it")
+            {
+                // making the call to from_json throw in order to check capacity
+                std::vector<float> v;
+                CHECK_THROWS_AS(nlohmann::from_json(j, v), std::logic_error);
+                CHECK(v.capacity() == j.size());
+            }
+#endif
         }
 
         SECTION("std::deque<json>")
@@ -184,6 +202,8 @@ TEST_CASE("value conversion")
             CHECK_THROWS_AS(json(json::value_t::number_unsigned).get<json::array_t>(), std::logic_error);
             CHECK_THROWS_AS(json(json::value_t::number_float).get<json::array_t>(), std::logic_error);
 
+            CHECK_THROWS_WITH(json(json::value_t::object).get<std::vector<int>>(),
+                              "type must be array, but is object");
             CHECK_THROWS_WITH(json(json::value_t::null).get<json::array_t>(),
                               "type must be array, but is null");
             CHECK_THROWS_WITH(json(json::value_t::object).get<json::array_t>(),
@@ -1004,6 +1024,8 @@ TEST_CASE("value conversion")
                 CHECK_THROWS_AS((json().get<std::vector<json>>()), std::logic_error);
                 CHECK_THROWS_AS((json().get<std::list<json>>()), std::logic_error);
 
+                // does type really must be an array? or it rather must not be null?
+                // that's what I thought when other test like this one broke
                 CHECK_THROWS_WITH((json().get<std::list<int>>()), "type must be array, but is null");
                 CHECK_THROWS_WITH((json().get<std::vector<int>>()), "type must be array, but is null");
                 CHECK_THROWS_WITH((json().get<std::vector<json>>()), "type must be array, but is null");
diff --git a/test/src/unit-noexcept.cpp b/test/src/unit-noexcept.cpp
new file mode 100644
index 00000000..b939db4e
--- /dev/null
+++ b/test/src/unit-noexcept.cpp
@@ -0,0 +1,32 @@
+#include "catch.hpp"
+
+#include "json.hpp"
+
+using nlohmann::json;
+
+enum test
+{
+};
+
+struct pod {};
+struct pod_bis {};
+
+void to_json(json&, pod) noexcept;
+void to_json(json&, pod_bis);
+void from_json(const json&, pod) noexcept;
+void from_json(const json&, pod_bis);
+static json j;
+
+static_assert(noexcept(json{}), "");
+static_assert(noexcept(nlohmann::to_json(j, 2)), "");
+static_assert(noexcept(nlohmann::to_json(j, 2.5)), "");
+static_assert(noexcept(nlohmann::to_json(j, true)), "");
+static_assert(noexcept(nlohmann::to_json(j, test{})), "");
+static_assert(noexcept(nlohmann::to_json(j, pod{})), "");
+static_assert(not noexcept(nlohmann::to_json(j, pod_bis{})), "");
+static_assert(noexcept(json(2)), "");
+static_assert(noexcept(json(test{})), "");
+static_assert(noexcept(json(pod{})), "");
+static_assert(noexcept(j.get<pod>()), "");
+static_assert(not noexcept(j.get<pod_bis>()), "");
+static_assert(noexcept(json(pod{})), "");
diff --git a/test/src/unit-regression.cpp b/test/src/unit-regression.cpp
index 7cb9169f..6a5e0c64 100644
--- a/test/src/unit-regression.cpp
+++ b/test/src/unit-regression.cpp
@@ -63,10 +63,18 @@ TEST_CASE("regression tests")
 
     SECTION("pull request #71 - handle enum type")
     {
-        enum { t = 0 };
+        enum { t = 0, u = 1};
         json j = json::array();
         j.push_back(t);
 
+        // maybe this is not the place to test this?
+        json j2 = u;
+
+        auto anon_enum_value = j2.get<decltype(u)>();
+        CHECK(u == anon_enum_value);
+
+        static_assert(std::is_same<decltype(anon_enum_value), decltype(u)>::value, "");
+
         j.push_back(json::object(
         {
             {"game_type", t}
diff --git a/test/src/unit-udt.cpp b/test/src/unit-udt.cpp
new file mode 100644
index 00000000..241336f3
--- /dev/null
+++ b/test/src/unit-udt.cpp
@@ -0,0 +1,680 @@
+/*
+    __ _____ _____ _____
+ __|  |   __|     |   | |  JSON for Modern C++ (test suite)
+|  |  |__   |  |  | | | |  version 2.0.7
+|_____|_____|_____|_|___|  https://github.com/nlohmann/json
+
+Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+Copyright (c) 2013-2016 Niels Lohmann <http://nlohmann.me>.
+
+Permission is hereby  granted, free of charge, to any  person obtaining a copy
+of this software and associated  documentation files (the "Software"), to deal
+in the Software  without restriction, including without  limitation the rights
+to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell
+copies  of  the Software,  and  to  permit persons  to  whom  the Software  is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE  IS PROVIDED "AS  IS", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR
+IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,
+FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE
+AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
+LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#include <array>
+#include <map>
+#include <string>
+#include <memory>
+#include "catch.hpp"
+
+#include "json.hpp"
+
+using nlohmann::json;
+
+namespace udt
+{
+enum class country
+{
+    china,
+    france,
+    russia
+};
+
+struct age
+{
+    int m_val;
+};
+
+struct name
+{
+    std::string m_val;
+};
+
+struct address
+{
+    std::string m_val;
+};
+
+struct person
+{
+    age m_age;
+    name m_name;
+    country m_country;
+};
+
+struct contact
+{
+    person m_person;
+    address m_address;
+};
+
+struct contact_book
+{
+    name m_book_name;
+    std::vector<contact> m_contacts;
+};
+}
+
+// to_json methods
+namespace udt
+{
+// templates because of the custom_json tests (see below)
+template <typename BasicJsonType>
+void to_json(BasicJsonType& j, age a)
+{
+    j = a.m_val;
+}
+
+template <typename BasicJsonType>
+void to_json(BasicJsonType& j, const name& n)
+{
+    j = n.m_val;
+}
+
+template <typename BasicJsonType>
+void to_json(BasicJsonType& j, country c)
+{
+    switch (c)
+    {
+        case country::china:
+            j = u8"中华人民共和国";
+            return;
+        case country::france:
+            j = "France";
+            return;
+        case country::russia:
+            j = u8"Российская Федерация";
+            return;
+    }
+}
+
+template <typename BasicJsonType>
+void to_json(BasicJsonType& j, const person& p)
+{
+    j = BasicJsonType{{"age", p.m_age}, {"name", p.m_name}, {"country", p.m_country}};
+}
+
+void to_json(nlohmann::json& j, const address& a)
+{
+    j = a.m_val;
+}
+
+void to_json(nlohmann::json& j, const contact& c)
+{
+    j = json{{"person", c.m_person}, {"address", c.m_address}};
+}
+
+void to_json(nlohmann::json& j, const contact_book& cb)
+{
+    j = json{{"name", cb.m_book_name}, {"contacts", cb.m_contacts}};
+}
+
+// operators
+bool operator==(age lhs, age rhs)
+{
+    return lhs.m_val == rhs.m_val;
+}
+
+bool operator==(const address& lhs, const address& rhs)
+{
+    return lhs.m_val == rhs.m_val;
+}
+
+bool operator==(const name& lhs, const name& rhs)
+{
+    return lhs.m_val == rhs.m_val;
+}
+
+bool operator==(const person& lhs, const person& rhs)
+{
+    return std::tie(lhs.m_name, lhs.m_age) == std::tie(rhs.m_name, rhs.m_age);
+}
+
+bool operator==(const contact& lhs, const contact& rhs)
+{
+    return std::tie(lhs.m_person, lhs.m_address) ==
+           std::tie(rhs.m_person, rhs.m_address);
+}
+
+bool operator==(const contact_book& lhs, const contact_book& rhs)
+{
+    return std::tie(lhs.m_book_name, lhs.m_contacts) ==
+           std::tie(rhs.m_book_name, rhs.m_contacts);
+}
+}
+
+// from_json methods
+namespace udt
+{
+template <typename BasicJsonType>
+void from_json(const BasicJsonType& j, age& a)
+{
+    a.m_val = j.template get<int>();
+}
+
+template <typename BasicJsonType>
+void from_json(const BasicJsonType& j, name& n)
+{
+    n.m_val = j.template get<std::string>();
+}
+
+template <typename BasicJsonType>
+void from_json(const BasicJsonType& j, country& c)
+{
+    const auto str = j.template get<std::string>();
+    static const std::map<std::string, country> m =
+    {
+        {u8"中华人民共和国", country::china},
+        {"France", country::france},
+        {"Российская Федерация", country::russia}
+    };
+
+    const auto it = m.find(str);
+    // TODO test exceptions
+    c = it->second;
+}
+
+template <typename BasicJsonType>
+void from_json(const BasicJsonType& j, person& p)
+{
+    p.m_age = j["age"].template get<age>();
+    p.m_name = j["name"].template get<name>();
+    p.m_country = j["country"].template get<country>();
+}
+
+void from_json(const nlohmann::json& j, address& a)
+{
+    a.m_val = j.get<std::string>();
+}
+
+void from_json(const nlohmann::json& j, contact& c)
+{
+    c.m_person = j["person"].get<person>();
+    c.m_address = j["address"].get<address>();
+}
+
+void from_json(const nlohmann::json& j, contact_book& cb)
+{
+    cb.m_book_name = j["name"].get<name>();
+    cb.m_contacts = j["contacts"].get<std::vector<contact>>();
+}
+}
+
+TEST_CASE("basic usage", "[udt]")
+{
+
+    // a bit narcissic maybe :) ?
+    const udt::age a
+    {
+        23
+    };
+    const udt::name n{"theo"};
+    const udt::country c{udt::country::france};
+    const udt::person sfinae_addict{a, n, c};
+    const udt::person senior_programmer{{42}, {u8"王芳"}, udt::country::china};
+    const udt::address addr{"Paris"};
+    const udt::contact cpp_programmer{sfinae_addict, addr};
+    const udt::contact_book book{{"C++"}, {cpp_programmer, {senior_programmer, addr}}};
+
+    SECTION("conversion to json via free-functions")
+    {
+        CHECK(json(a) == json(23));
+        CHECK(json(n) == json("theo"));
+        CHECK(json(c) == json("France"));
+        CHECK(json(sfinae_addict) == R"({"name":"theo", "age":23, "country":"France"})"_json);
+        CHECK(json("Paris") == json(addr));
+        CHECK(json(cpp_programmer) ==
+              R"({"person" : {"age":23, "name":"theo", "country":"France"}, "address":"Paris"})"_json);
+
+        CHECK(
+            json(book) ==
+            u8R"({"name":"C++", "contacts" : [{"person" : {"age":23, "name":"theo", "country":"France"}, "address":"Paris"}, {"person" : {"age":42, "country":"中华人民共和国", "name":"王芳"}, "address":"Paris"}]})"_json);
+
+    }
+
+    SECTION("conversion from json via free-functions")
+    {
+        const auto big_json =
+            u8R"({"name":"C++", "contacts" : [{"person" : {"age":23, "name":"theo", "country":"France"}, "address":"Paris"}, {"person" : {"age":42, "country":"中华人民共和国", "name":"王芳"}, "address":"Paris"}]})"_json;
+        SECTION("via explicit calls to get")
+        {
+            const auto parsed_book = big_json.get<udt::contact_book>();
+            const auto book_name = big_json["name"].get<udt::name>();
+            const auto contacts =
+                big_json["contacts"].get<std::vector<udt::contact>>();
+            const auto contact_json = big_json["contacts"].at(0);
+            const auto contact = contact_json.get<udt::contact>();
+            const auto person = contact_json["person"].get<udt::person>();
+            const auto address = contact_json["address"].get<udt::address>();
+            const auto age = contact_json["person"]["age"].get<udt::age>();
+            const auto country =
+                contact_json["person"]["country"].get<udt::country>();
+            const auto name = contact_json["person"]["name"].get<udt::name>();
+
+            CHECK(age == a);
+            CHECK(name == n);
+            CHECK(country == c);
+            CHECK(address == addr);
+            CHECK(person == sfinae_addict);
+            CHECK(contact == cpp_programmer);
+            CHECK(contacts == book.m_contacts);
+            CHECK(book_name == udt::name{"C++"});
+            CHECK(book == parsed_book);
+        }
+
+        SECTION("implicit conversions")
+        {
+            const udt::contact_book parsed_book = big_json;
+            const udt::name book_name = big_json["name"];
+            const std::vector<udt::contact> contacts = big_json["contacts"];
+            const auto contact_json = big_json["contacts"].at(0);
+            const udt::contact contact = contact_json;
+            const udt::person person = contact_json["person"];
+            const udt::address address = contact_json["address"];
+            const udt::age age = contact_json["person"]["age"];
+            const udt::country country = contact_json["person"]["country"];
+            const udt::name name = contact_json["person"]["name"];
+
+            CHECK(age == a);
+            CHECK(name == n);
+            CHECK(country == c);
+            CHECK(address == addr);
+            CHECK(person == sfinae_addict);
+            CHECK(contact == cpp_programmer);
+            CHECK(contacts == book.m_contacts);
+            CHECK(book_name == udt::name{"C++"});
+            CHECK(book == parsed_book);
+        }
+    }
+}
+
+namespace udt
+{
+struct legacy_type
+{
+    std::string number;
+};
+}
+
+namespace nlohmann
+{
+template <typename T>
+struct adl_serializer<std::shared_ptr<T>>
+{
+    static void to_json(json& j, const std::shared_ptr<T>& opt)
+    {
+        if (opt)
+        {
+            j = *opt;
+        }
+        else
+        {
+            j = nullptr;
+        }
+    }
+
+    static void from_json(const json& j, std::shared_ptr<T>& opt)
+    {
+        if (j.is_null())
+        {
+            opt = nullptr;
+        }
+        else
+        {
+            opt.reset(new T(j.get<T>()));
+        }
+    }
+};
+
+template <>
+struct adl_serializer<udt::legacy_type>
+{
+    static void to_json(json& j, const udt::legacy_type& l)
+    {
+        j = std::stoi(l.number);
+    }
+
+    static void from_json(const json& j, udt::legacy_type& l)
+    {
+        l.number = std::to_string(j.get<int>());
+    }
+};
+}
+
+TEST_CASE("adl_serializer specialization", "[udt]")
+{
+    SECTION("partial specialization")
+    {
+        SECTION("to_json")
+        {
+            std::shared_ptr<udt::person> optPerson;
+
+            json j = optPerson;
+            CHECK(j.is_null());
+
+            optPerson.reset(new udt::person{{42}, {"John Doe"}, udt::country::russia});
+            j = optPerson;
+            CHECK_FALSE(j.is_null());
+
+            CHECK(j.get<udt::person>() == *optPerson);
+        }
+
+        SECTION("from_json")
+        {
+            auto person = udt::person{{42}, {"John Doe"}, udt::country::russia};
+            json j = person;
+
+            auto optPerson = j.get<std::shared_ptr<udt::person>>();
+            REQUIRE(optPerson);
+            CHECK(*optPerson == person);
+
+            j = nullptr;
+            optPerson = j.get<std::shared_ptr<udt::person>>();
+            CHECK(!optPerson);
+        }
+    }
+
+    SECTION("total specialization")
+    {
+        SECTION("to_json")
+        {
+            udt::legacy_type lt{"4242"};
+
+            json j = lt;
+            CHECK(j.get<int>() == 4242);
+        }
+
+        SECTION("from_json")
+        {
+            json j = 4242;
+            auto lt = j.get<udt::legacy_type>();
+            CHECK(lt.number == "4242");
+        }
+    }
+}
+
+namespace nlohmann
+{
+template <>
+struct adl_serializer<std::vector<float>>
+{
+    using type = std::vector<float>;
+    static void to_json(json& j, const type&)
+    {
+        j = "hijacked!";
+    }
+
+    static void from_json(const json&, type& opt)
+    {
+        opt = {42.0, 42.0, 42.0};
+    }
+
+    // preferred version
+    static type from_json(const json&)
+    {
+        return {4.0, 5.0, 6.0};
+    }
+};
+}
+
+TEST_CASE("even supported types can be specialized", "[udt]")
+{
+    json j = std::vector<float> {1.0, 2.0, 3.0};
+    CHECK(j.dump() == R"("hijacked!")");
+    auto f = j.get<std::vector<float>>();
+    // the single argument from_json method is preferred
+    CHECK((f == std::vector<float> {4.0, 5.0, 6.0}));
+}
+
+namespace nlohmann
+{
+template <typename T>
+struct adl_serializer<std::unique_ptr<T>>
+{
+    static void to_json(json& j, const std::unique_ptr<T>& opt)
+    {
+        if (opt)
+        {
+            j = *opt;
+        }
+        else
+        {
+            j = nullptr;
+        }
+    }
+
+    // this is the overload needed for non-copyable types,
+    static std::unique_ptr<T> from_json(const json& j)
+    {
+        if (j.is_null())
+        {
+            return nullptr;
+        }
+        else
+        {
+            return std::unique_ptr<T>(new T(j.get<T>()));
+        }
+    }
+};
+}
+
+TEST_CASE("Non-copyable types", "[udt]")
+{
+    SECTION("to_json")
+    {
+        std::unique_ptr<udt::person> optPerson;
+
+        json j = optPerson;
+        CHECK(j.is_null());
+
+        optPerson.reset(new udt::person{{42}, {"John Doe"}, udt::country::russia});
+        j = optPerson;
+        CHECK_FALSE(j.is_null());
+
+        CHECK(j.get<udt::person>() == *optPerson);
+    }
+
+    SECTION("from_json")
+    {
+        auto person = udt::person{{42}, {"John Doe"}, udt::country::russia};
+        json j = person;
+
+        auto optPerson = j.get<std::unique_ptr<udt::person>>();
+        REQUIRE(optPerson);
+        CHECK(*optPerson == person);
+
+        j = nullptr;
+        optPerson = j.get<std::unique_ptr<udt::person>>();
+        CHECK(!optPerson);
+    }
+}
+
+// custom serializer - advanced usage
+// pack structs that are pod-types (but not scalar types)
+// relies on adl for any other type
+template <typename T, typename = void>
+struct pod_serializer
+{
+    // use adl for non-pods, or scalar types
+    template <
+        typename BasicJsonType, typename U = T,
+        typename std::enable_if <
+            not(std::is_pod<U>::value and std::is_class<U>::value), int >::type = 0 >
+    static void from_json(const BasicJsonType& j, U& t)
+    {
+        using nlohmann::from_json;
+        from_json(j, t);
+    }
+
+    // special behaviour for pods
+    template <typename BasicJsonType, typename U = T,
+              typename std::enable_if<
+                  std::is_pod<U>::value and std::is_class<U>::value, int>::type = 0>
+    static void from_json(const  BasicJsonType& j, U& t)
+    {
+        std::uint64_t value;
+        // TODO The following block is no longer relevant in this serializer, make another one that shows the issue
+        // the problem arises only when one from_json method is defined without any constraint
+        //
+        // Why cannot we simply use: j.get<std::uint64_t>() ?
+        // Well, with the current experiment, the get method looks for a from_json
+        // function, which we are currently defining!
+        // This would end up in a stack overflow. Calling nlohmann::from_json is a
+        // workaround (is it?).
+        // I shall find a good way to avoid this once all constructors are converted
+        // to free methods
+        //
+        // In short, constructing a json by constructor calls to_json
+        // calling get calls from_json, for now, we cannot do this in custom
+        // serializers
+        nlohmann::from_json(j, value);
+        auto bytes = static_cast<char*>(static_cast<void*>(&value));
+        std::memcpy(&t, bytes, sizeof(value));
+    }
+
+    template <
+        typename BasicJsonType, typename U = T,
+        typename std::enable_if <
+            not(std::is_pod<U>::value and std::is_class<U>::value), int >::type = 0 >
+    static void to_json(BasicJsonType& j, const  T& t)
+    {
+        using nlohmann::to_json;
+        to_json(j, t);
+    }
+
+    template <typename BasicJsonType, typename U = T,
+              typename std::enable_if<
+                  std::is_pod<U>::value and std::is_class<U>::value, int>::type = 0>
+    static void to_json(BasicJsonType& j, const  T& t) noexcept
+    {
+        auto bytes = static_cast< const unsigned char*>(static_cast<const void*>(&t));
+        std::uint64_t value = bytes[0];
+        for (auto i = 1; i < 8; ++i)
+            value |= std::uint64_t{bytes[i]} << 8 * i;
+        nlohmann::to_json(j, value);
+    }
+};
+
+namespace udt
+{
+struct small_pod
+{
+    int begin;
+    char middle;
+    short end;
+};
+
+struct non_pod
+{
+    std::string s;
+};
+
+template <typename BasicJsonType>
+void to_json(BasicJsonType& j, const non_pod& np)
+{
+    j = np.s;
+}
+
+template <typename BasicJsonType>
+void from_json(const BasicJsonType& j, non_pod& np)
+{
+    np.s = j.template get<std::string>();
+}
+
+bool operator==(small_pod lhs, small_pod rhs) noexcept
+{
+    return std::tie(lhs.begin, lhs.middle, lhs.end) ==
+           std::tie(rhs.begin, rhs.middle, rhs.end);
+}
+
+bool operator==(const  non_pod& lhs, const  non_pod& rhs) noexcept
+{
+    return lhs.s == rhs.s;
+}
+
+std::ostream& operator<<(std::ostream& os, small_pod l)
+{
+    return os << "begin: " << l.begin << ", middle: " << l.middle << ", end: " << l.end;
+}
+}
+
+TEST_CASE("custom serializer for pods", "[udt]")
+{
+    using custom_json =
+        nlohmann::basic_json<std::map, std::vector, std::string, bool,
+        std::int64_t, std::uint64_t, double, std::allocator,
+        pod_serializer>;
+
+    auto p = udt::small_pod{42, '/', 42};
+    custom_json j = p;
+
+    auto p2 = j.get<udt::small_pod>();
+
+    CHECK(p == p2);
+
+    auto np = udt::non_pod{{"non-pod"}};
+    custom_json j2 = np;
+    auto np2 = j2.get<udt::non_pod>();
+    CHECK(np == np2);
+}
+
+template <typename T, typename>
+struct another_adl_serializer;
+
+using custom_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, double, std::allocator, another_adl_serializer>;
+
+template <typename T, typename>
+struct another_adl_serializer
+{
+    static void from_json(const custom_json& j, T& t)
+    {
+        using nlohmann::from_json;
+        from_json(j, t);
+    }
+
+    static void to_json(custom_json& j, const T& t)
+    {
+        using nlohmann::to_json;
+        to_json(j, t);
+    }
+};
+
+TEST_CASE("custom serializer that does adl by default", "[udt]")
+{
+    using json = nlohmann::json;
+
+    auto me = udt::person{{23}, {"theo"}, udt::country::france};
+
+    json j = me;
+    custom_json cj = me;
+
+    CHECK(j.dump() == cj.dump());
+
+    CHECK(me == j.get<udt::person>());
+    CHECK(me == cj.get<udt::person>());
+}