diff --git a/src/json.hpp b/src/json.hpp
index 1db82e12..99a6e854 100644
--- a/src/json.hpp
+++ b/src/json.hpp
@@ -6605,6 +6605,7 @@ class basic_json
     // output adapters //
     /////////////////////
 
+    /// abstract output adapter interface
     template<typename CharType>
     class output_adapter
     {
@@ -6629,9 +6630,11 @@ class basic_json
         }
     };
 
+    /// a type to simplify interfaces
     template<typename CharType>
     using output_adapter_t = std::shared_ptr<output_adapter<CharType>>;
 
+    /// output adapter for byte vectors
     template<typename CharType>
     class output_vector_adapter : public output_adapter<CharType>
     {
@@ -6654,6 +6657,7 @@ class basic_json
         std::vector<CharType>& v;
     };
 
+    /// putput adatpter for output streams
     template<typename CharType>
     class output_stream_adapter : public output_adapter<CharType>
     {
@@ -6676,6 +6680,7 @@ class basic_json
         std::basic_ostream<CharType>& stream;
     };
 
+    /// output adapter for basic_string
     template<typename CharType>
     class output_string_adapter : public output_adapter<CharType>
     {
@@ -8773,7 +8778,7 @@ class basic_json
         }
     };
 
-    // a type to simplify interfaces
+    /// a type to simplify interfaces
     using input_adapter_t = std::shared_ptr<input_adapter>;
 
     /// input adapter for cached stream input
@@ -8930,17 +8935,34 @@ class basic_json
     /// @{
 
   private:
+    /*!
+    @brief deserialization of CBOR and MessagePack values
+    */
     class binary_reader
     {
       public:
+        /*!
+        @brief create a binary reader
+
+        @param[in] adapter  input adapter to read from
+        */
         explicit binary_reader(input_adapter_t adapter)
             : ia(adapter), is_little_endian(little_endianess())
-        {}
+        {
+            assert(ia);
+        }
 
         /*!
+        @brief create a JSON value from CBOR input
+
         @param[in] get_char  whether a new character should be retrieved from
                              the input (true, default) or whether the last
                              read character should be considered instead
+
+        @return JSON value created from CBOR input
+
+        @throw parse_error.110 if input ended unexpectedly
+        @throw parse_error.112 if unsupported byte was read
         */
         basic_json parse_cbor(const bool get_char = true)
         {
@@ -9297,12 +9319,13 @@ class basic_json
                     check_eof();
 
                     // code from RFC 7049, Appendix D, Figure 3:
-                    // As half-precision floating-point numbers were only added to
-                    // IEEE 754 in 2008, today's programming platforms often still
-                    // only have limited support for them. It is very easy to
-                    // include at least decoding support for them even without such
-                    // support. An example of a small decoder for half-precision
-                    // floating-point numbers in the C language is shown in Fig. 3.
+                    // As half-precision floating-point numbers were only added
+                    // to IEEE 754 in 2008, today's programming platforms often
+                    // still only have limited support for them. It is very
+                    // easy to include at least decoding support for them even
+                    // without such support. An example of a small decoder for
+                    // half-precision floating-point numbers in the C language
+                    // is shown in Fig. 3.
                     const int half = (byte1 << 8) + byte2;
                     const int exp = (half >> 10) & 0x1f;
                     const int mant = half & 0x3ff;
@@ -9343,6 +9366,14 @@ class basic_json
             }
         }
 
+        /*!
+        @brief create a JSON value from MessagePack input
+
+        @return JSON value created from MessagePack input
+
+        @throw parse_error.110 if input ended unexpectedly
+        @throw parse_error.112 if unsupported byte was read
+        */
         basic_json parse_msgpack()
         {
             switch (get())
@@ -9745,23 +9776,52 @@ class basic_json
             }
         }
 
-      private:
-        // from http://stackoverflow.com/a/1001328/266378
-        static bool little_endianess()
+        /*!
+        @brief determine system byte order
+
+        @return true iff system's byte order is little endian
+
+        @note from http://stackoverflow.com/a/1001328/266378
+        */
+        static bool little_endianess() noexcept
         {
             int num = 1;
             return (*reinterpret_cast<char*>(&num) == 1);
         }
 
+      private:
+        /*!
+        @brief get next character from the input
+
+        This function provides the interface to the used input adapter. It does
+        not throw in case the input reached EOF, but returns
+        `std::char_traits<char>::eof()` in that case.
+
+        @return character read from the input
+        */
         int get()
         {
             ++chars_read;
             return (current = ia->get_character());
         }
 
+        /*
+        @brief read a number from the input
+
+        @tparam T the type of the number
+
+        @return number of type @a T
+
+        @note This function needs to respect the system's endianess, because
+              bytes in CBOR and MessagePack are stored in network order (big
+              endian) and therefore need reordering on little endian systems.
+
+        @throw parse_error.110 if input has less than `sizeof(T)` bytes
+        */
         template<typename T>
         T get_number()
         {
+            // step 1: read input into array with system's byte order
             std::array<uint8_t, sizeof(T)> vec;
             for (size_t i = 0; i < sizeof(T); ++i)
             {
@@ -9779,11 +9839,21 @@ class basic_json
                 }
             }
 
+            // step 2: convert array into number of type T and return
             T result;
             std::memcpy(&result, vec.data(), sizeof(T));
             return result;
         }
 
+        /*!
+        @brief create a string by reading characters from the input
+
+        @param[in] len number of bytes to read
+
+        @return string created by reading @a len bytes
+
+        @throw parse_error.110 if input has less than @a len bytes
+        */
         std::string get_string(const size_t len)
         {
             std::string result;
@@ -9796,6 +9866,18 @@ class basic_json
             return result;
         }
 
+        /*!
+        @brief reads a CBOR string
+
+        This function first reads starting bytes to determine the expected
+        string length and then copies this number of bytes into a string.
+        Additionally, CBOR's strings with indefinite lengths are supported.
+
+        @return string
+
+        @throw parse_error.110 if input ended
+        @throw parse_error.113 if an unexpexted byte is read
+        */
         std::string get_cbor_string()
         {
             check_eof();
@@ -9876,6 +9958,17 @@ class basic_json
             }
         }
 
+        /*!
+        @brief reads a MessagePack string
+
+        This function first reads starting bytes to determine the expected
+        string length and then copies this number of bytes into a string.
+
+        @return string
+
+        @throw parse_error.110 if input ended
+        @throw parse_error.113 if an unexpexted byte is read
+        */
         std::string get_msgpack_string()
         {
             check_eof();
@@ -9947,7 +10040,11 @@ class basic_json
             }
         }
 
-        void check_eof()
+        /*!
+        @brief check if input ended
+        @throw parse_error.110 if input ended
+        */
+        void check_eof() const
         {
             if (JSON_UNLIKELY(current == std::char_traits<char>::eof()))
             {
@@ -9969,17 +10066,26 @@ class basic_json
         const bool is_little_endian = true;
     };
 
+    /*!
+    @brief serialization to CBOR and MessagePack values
+    */
     class binary_writer
     {
       public:
-        binary_writer()
-            : is_little_endian(little_endianess())
-        {}
+        /*!
+        @brief create a binary writer
 
+        @param[in] adapter  output adapter to write to
+        */
         explicit binary_writer(output_adapter_t<uint8_t> adapter)
-            : is_little_endian(little_endianess()), oa(adapter)
-        {}
+            : is_little_endian(binary_reader::little_endianess()), oa(adapter)
+        {
+            assert(oa);
+        }
 
+        /*!
+        @brief[in] j  JSON value to serialize
+        */
         void write_cbor(const basic_json& j)
         {
             switch (j.type())
@@ -10100,6 +10206,7 @@ class basic_json
 
                 case value_t::string:
                 {
+                    // step 1: write control byte and the string length
                     const auto N = j.m_value.string->size();
                     if (N <= 0x17)
                     {
@@ -10128,7 +10235,7 @@ class basic_json
                     }
                     // LCOV_EXCL_STOP
 
-                    // append string
+                    // step 2: write the string
                     oa->write_characters(reinterpret_cast<const uint8_t*>(j.m_value.string->c_str()),
                                          j.m_value.string->size());
                     break;
@@ -10136,6 +10243,7 @@ class basic_json
 
                 case value_t::array:
                 {
+                    // step 1: write control byte and the array size
                     const auto N = j.m_value.array->size();
                     if (N <= 0x17)
                     {
@@ -10164,7 +10272,7 @@ class basic_json
                     }
                     // LCOV_EXCL_STOP
 
-                    // append each element
+                    // step 2: write each element
                     for (const auto& el : *j.m_value.array)
                     {
                         write_cbor(el);
@@ -10174,6 +10282,7 @@ class basic_json
 
                 case value_t::object:
                 {
+                    // step 1: write control byte and the object size
                     const auto N = j.m_value.object->size();
                     if (N <= 0x17)
                     {
@@ -10202,7 +10311,7 @@ class basic_json
                     }
                     // LCOV_EXCL_STOP
 
-                    // append each element
+                    // step 2: write each element
                     for (const auto& el : *j.m_value.object)
                     {
                         write_cbor(el.first);
@@ -10218,6 +10327,9 @@ class basic_json
             }
         }
 
+        /*!
+        @brief[in] j  JSON value to serialize
+        */
         void write_msgpack(const basic_json& j)
         {
             switch (j.type())
@@ -10353,6 +10465,7 @@ class basic_json
 
                 case value_t::string:
                 {
+                    // step 1: write control byte and the string length
                     const auto N = j.m_value.string->size();
                     if (N <= 31)
                     {
@@ -10378,7 +10491,7 @@ class basic_json
                         write_number(static_cast<uint32_t>(N));
                     }
 
-                    // append string
+                    // step 2: write the string
                     oa->write_characters(reinterpret_cast<const uint8_t*>(j.m_value.string->c_str()),
                                          j.m_value.string->size());
                     break;
@@ -10386,6 +10499,7 @@ class basic_json
 
                 case value_t::array:
                 {
+                    // step 1: write control byte and the array size
                     const auto N = j.m_value.array->size();
                     if (N <= 15)
                     {
@@ -10405,7 +10519,7 @@ class basic_json
                         write_number(static_cast<uint32_t>(N));
                     }
 
-                    // append each element
+                    // step 2: write each element
                     for (const auto& el : *j.m_value.array)
                     {
                         write_msgpack(el);
@@ -10415,6 +10529,7 @@ class basic_json
 
                 case value_t::object:
                 {
+                    // step 1: write control byte and the object size
                     const auto N = j.m_value.object->size();
                     if (N <= 15)
                     {
@@ -10434,7 +10549,7 @@ class basic_json
                         write_number(static_cast<uint32_t>(N));
                     }
 
-                    // append each element
+                    // step 2: write each element
                     for (const auto& el : *j.m_value.object)
                     {
                         write_msgpack(el.first);
@@ -10451,12 +10566,24 @@ class basic_json
         }
 
       private:
+        /*
+        @brief write a number to output input
+
+        @param[in] n number of type @a T
+        @tparam T the type of the number
+
+        @note This function needs to respect the system's endianess, because
+              bytes in CBOR and MessagePack are stored in network order (big
+              endian) and therefore need reordering on little endian systems.
+        */
         template<typename T>
         void write_number(T n)
         {
+            // step 1: write number to array of length T
             std::array<uint8_t, sizeof(T)> vec;
             std::memcpy(vec.data(), &n, sizeof(T));
 
+            // step 2: write array to output (with possible reordering)
             for (size_t i = 0; i < sizeof(T); ++i)
             {
                 // reverse byte order prior to conversion if necessary
@@ -10471,13 +10598,6 @@ class basic_json
             }
         }
 
-        // from http://stackoverflow.com/a/1001328/266378
-        static bool little_endianess()
-        {
-            int num = 1;
-            return (*reinterpret_cast<char*>(&num) == 1);
-        }
-
       private:
         /// whether we can assume little endianess
         const bool is_little_endian = true;
@@ -10928,12 +11048,19 @@ class basic_json
         // scan functions
         /////////////////////
 
-        // must be called after \u was read; returns following xxxx as hex or -1 when error
+        /*!
+        @brief get codepoint from 4 hex characters following `\u`
+
+        @return codepoint or -1 in case of an error (e.g. EOF or non-hex
+                character)
+        */
         int get_codepoint()
         {
+            // this function only makes sense after reading `\u`
             assert(current == 'u');
             int codepoint = 0;
 
+            // byte 1: \uXxxx
             switch (get())
             {
                 case '0':
@@ -10993,6 +11120,7 @@ class basic_json
                     return -1;
             }
 
+            // byte 2: \uxXxx
             switch (get())
             {
                 case '0':
@@ -11052,6 +11180,7 @@ class basic_json
                     return -1;
             }
 
+            // byte 3: \uxxXx
             switch (get())
             {
                 case '0':
@@ -11111,6 +11240,7 @@ class basic_json
                     return -1;
             }
 
+            // byte 4: \uxxxX
             switch (get())
             {
                 case '0':
@@ -11173,6 +11303,10 @@ class basic_json
             return codepoint;
         }
 
+        /*!
+        @brief create diagnostic representation of a codepoint
+        @return string "U+XXXX" for codepoint XXXX
+        */
         static std::string codepoint_to_string(int codepoint)
         {
             std::stringstream ss;
@@ -11180,6 +11314,20 @@ class basic_json
             return ss.str();
         }
 
+        /*!
+        @brief scan a string literal
+
+        This function scans a string according to Sect. 7 of RFC 7159. While
+        scanning, bytes are escaped and copied into buffer yytext. Then the
+        function returns successfully, yytext is null-terminated and yylen
+        contains the number of bytes in the string.
+
+        @return token_type::value_string if string could be successfully
+                scanned, token_type::parse_error otherwise
+
+        @note In case of errors, variable error_message contains a textual
+              description.
+        */
         token_type scan_string()
         {
             // reset yytext (ignore opening quote)
@@ -11714,6 +11862,17 @@ class basic_json
         }
 
         /*!
+        @brief scan a number literal
+
+        This function scans a string according to Sect. 6 of RFC 7159.
+
+        The function is realized with a deterministic finite state machine
+        derived from the grammar described in RFC 7159. Starting in state
+        "init", the input is read and used to determined the next state. Only
+        state "done" accepts the number. State "error" is a trap state to model
+        errors. In the table below, "anything" means any character but the ones
+        listed before.
+
         state    | 0        | 1-9      | e E      | +       | -       | .        | anything
         ---------|----------|----------|----------|---------|---------|----------|-----------
         init     | zero     | any1     | [error]  | [error] | minus   | [error]  | [error]
@@ -11725,13 +11884,31 @@ class basic_json
         exponent | any2     | any2     | [error]  | sign    | sign    | [error]  | [error]
         sign     | any2     | any2     | [error]  | [error] | [error] | [error]  | [error]
         any2     | any2     | any2     | done     | done    | done    | done     | done
+
+        The state machine is realized with one label per state (prefixed with
+        "scan_number_") and `goto` statements between them. The state machine
+        contains cycles, but any cycle can be left when EOF is read. Therefore,
+        the function is guaranteed to terminate.
+
+        During scanning, the read bytes are stored in yytext. This string is
+        then converted to a signed integer, an unsigned integer, or a
+        floating-point number.
+
+        @return token_type::value_unsigned, token_type::value_integer, or
+                token_type::value_float if number could be successfully scanned,
+                token_type::parse_error otherwise
+
+        @note The scanner is independent of the current locale. Internally, the
+              locale's decimal point is used instead of `.` to work with the
+              locale-dependent converters.
         */
         token_type scan_number()
         {
+            // reset yytext to store the number's bytes
             reset();
 
-            // the type of the parsed number; initially set to unsigned; will
-            // be changed if minus sign, decimal point or exponent is read
+            // the type of the parsed number; initially set to unsigned; will be
+            // changed if minus sign, decimal point or exponent is read
             token_type number_type = token_type::value_unsigned;
 
             // state (init): we just found out we need to scan a number
@@ -12008,7 +12185,8 @@ scan_number_any2:
             }
 
 scan_number_done:
-            // unget the character after the number
+            // unget the character after the number (we only read it to know
+            // that we are done scanning a number)
             --chars_read;
             next_unget = true;
 
@@ -12155,7 +12333,7 @@ scan_number_done:
         const std::string get_string()
         {
             // yytext cannot be returned as char*, because it may contain a
-            // null byte
+            // null byte (parsed as "\u0000")
             return std::string(yytext.data(), yylen);
         }
 
@@ -12302,7 +12480,7 @@ scan_number_done:
         number_float_t value_float = 0;
 
         /// the decimal point
-        const char decimal_point_char = '\0';
+        const char decimal_point_char = '.';
     };
 
     /*!