/*
    __ _____ _____ _____
 __|  |   __|     |   | |  JSON for Modern C++ (test suite)
|  |  |__   |  |  | | | |  version 2.0.1
|_____|_____|_____|_|___|  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.
*/

#define CATCH_CONFIG_MAIN
#include "catch.hpp"

#include <array>
#include <deque>
#include <forward_list>
#include <list>
#include <map>
#include <set>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>

#define private public
#include "json.hpp"
using nlohmann::json;

// disable float-equal warnings on GCC/clang
#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__)
    #pragma GCC diagnostic ignored "-Wfloat-equal"
#endif

TEST_CASE("constructors")
{
    SECTION("create an empty value with a given type")
    {
        SECTION("null")
        {
            auto t = json::value_t::null;
            json j(t);
            CHECK(j.type() == t);
        }

        SECTION("discarded")
        {
            auto t = json::value_t::discarded;
            json j(t);
            CHECK(j.type() == t);
        }

        SECTION("object")
        {
            auto t = json::value_t::object;
            json j(t);
            CHECK(j.type() == t);
        }

        SECTION("array")
        {
            auto t = json::value_t::array;
            json j(t);
            CHECK(j.type() == t);
        }

        SECTION("boolean")
        {
            auto t = json::value_t::boolean;
            json j(t);
            CHECK(j.type() == t);
        }

        SECTION("string")
        {
            auto t = json::value_t::string;
            json j(t);
            CHECK(j.type() == t);
        }

        SECTION("number_integer")
        {
            auto t = json::value_t::number_integer;
            json j(t);
            CHECK(j.type() == t);
        }

        SECTION("number_unsigned")
        {
            auto t = json::value_t::number_unsigned;
            json j(t);
            CHECK(j.type() == t);
        }

        SECTION("number_float")
        {
            auto t = json::value_t::number_float;
            json j(t);
            CHECK(j.type() == t);
        }
    }

    SECTION("create a null object (implicitly)")
    {
        SECTION("no parameter")
        {
            json j{};
            CHECK(j.type() == json::value_t::null);
        }
    }

    SECTION("create a null object (explicitly)")
    {
        SECTION("parameter")
        {
            json j(nullptr);
            CHECK(j.type() == json::value_t::null);
        }
    }

    SECTION("create an object (explicit)")
    {
        SECTION("empty object")
        {
            json::object_t o;
            json j(o);
            CHECK(j.type() == json::value_t::object);
        }

        SECTION("filled object")
        {
            json::object_t o {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}};
            json j(o);
            CHECK(j.type() == json::value_t::object);
        }
    }

    SECTION("create an object (implicit)")
    {
        // reference object
        json::object_t o_reference {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}};
        json j_reference(o_reference);

        SECTION("std::map<json::string_t, json>")
        {
            std::map<json::string_t, json> o {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}};
            json j(o);
            CHECK(j.type() == json::value_t::object);
            CHECK(j == j_reference);
        }

        SECTION("std::map<const char*, json>")
        {
            std::map<const char*, json> o {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}};
            json j(o);
            CHECK(j.type() == json::value_t::object);
            CHECK(j == j_reference);
        }

        SECTION("std::multimap<json::string_t, json>")
        {
            std::multimap<json::string_t, json> o {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}};
            json j(o);
            CHECK(j.type() == json::value_t::object);
            CHECK(j == j_reference);
        }

        SECTION("std::unordered_map<json::string_t, json>")
        {
            std::unordered_map<json::string_t, json> o {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}};
            json j(o);
            CHECK(j.type() == json::value_t::object);
            CHECK(j == j_reference);
        }

        SECTION("std::unordered_multimap<json::string_t, json>")
        {
            std::unordered_multimap<json::string_t, json> o {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}};
            json j(o);
            CHECK(j.type() == json::value_t::object);
            CHECK(j == j_reference);
        }

        SECTION("associative container literal")
        {
            json j({{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}});
            CHECK(j.type() == json::value_t::object);
            CHECK(j == j_reference);
        }
    }

    SECTION("create an array (explicit)")
    {
        SECTION("empty array")
        {
            json::array_t a;
            json j(a);
            CHECK(j.type() == json::value_t::array);
        }

        SECTION("filled array")
        {
            json::array_t a {json(1), json(1u), json(2.2), json(false), json("string"), json()};
            json j(a);
            CHECK(j.type() == json::value_t::array);
        }
    }

    SECTION("create an array (implicit)")
    {
        // reference array
        json::array_t a_reference {json(1), json(1u), json(2.2), json(false), json("string"), json()};
        json j_reference(a_reference);

        SECTION("std::list<json>")
        {
            std::list<json> a {json(1), json(1u), json(2.2), json(false), json("string"), json()};
            json j(a);
            CHECK(j.type() == json::value_t::array);
            CHECK(j == j_reference);
        }

        SECTION("std::forward_list<json>")
        {
            std::forward_list<json> a {json(1), json(1u), json(2.2), json(false), json("string"), json()};
            json j(a);
            CHECK(j.type() == json::value_t::array);
            CHECK(j == j_reference);
        }

        SECTION("std::array<json, 5>")
        {
            std::array<json, 6> a {{json(1), json(1u), json(2.2), json(false), json("string"), json()}};
            json j(a);
            CHECK(j.type() == json::value_t::array);
            CHECK(j == j_reference);
        }

        SECTION("std::vector<json>")
        {
            std::vector<json> a {json(1), json(1u), json(2.2), json(false), json("string"), json()};
            json j(a);
            CHECK(j.type() == json::value_t::array);
            CHECK(j == j_reference);
        }

        SECTION("std::deque<json>")
        {
            std::deque<json> a {json(1), json(1u), json(2.2), json(false), json("string"), json()};
            json j(a);
            CHECK(j.type() == json::value_t::array);
            CHECK(j == j_reference);
        }

        SECTION("std::set<json>")
        {
            std::set<json> a {json(1), json(1u), json(2.2), json(false), json("string"), json()};
            json j(a);
            CHECK(j.type() == json::value_t::array);
            // we cannot really check for equality here
        }

        SECTION("std::unordered_set<json>")
        {
            std::unordered_set<json> a {json(1), json(1u), json(2.2), json(false), json("string"), json()};
            json j(a);
            CHECK(j.type() == json::value_t::array);
            // we cannot really check for equality here
        }

        SECTION("sequence container literal")
        {
            json j({json(1), json(1u), json(2.2), json(false), json("string"), json()});
            CHECK(j.type() == json::value_t::array);
            CHECK(j == j_reference);
        }
    }

    SECTION("create a string (explicit)")
    {
        SECTION("empty string")
        {
            json::string_t s;
            json j(s);
            CHECK(j.type() == json::value_t::string);
        }

        SECTION("filled string")
        {
            json::string_t s {"Hello world"};
            json j(s);
            CHECK(j.type() == json::value_t::string);
        }
    }

    SECTION("create a string (implicit)")
    {
        // reference string
        json::string_t s_reference {"Hello world"};
        json j_reference(s_reference);

        SECTION("std::string")
        {
            std::string s {"Hello world"};
            json j(s);
            CHECK(j.type() == json::value_t::string);
            CHECK(j == j_reference);
        }

        SECTION("char[]")
        {
            char s[] {"Hello world"};
            json j(s);
            CHECK(j.type() == json::value_t::string);
            CHECK(j == j_reference);
        }

        SECTION("const char*")
        {
            const char* s {"Hello world"};
            json j(s);
            CHECK(j.type() == json::value_t::string);
            CHECK(j == j_reference);
        }

        SECTION("string literal")
        {
            json j("Hello world");
            CHECK(j.type() == json::value_t::string);
            CHECK(j == j_reference);
        }
    }

    SECTION("create a boolean (explicit)")
    {
        SECTION("empty boolean")
        {
            json::boolean_t b{};
            json j(b);
            CHECK(j.type() == json::value_t::boolean);
        }

        SECTION("filled boolean (true)")
        {
            json j(true);
            CHECK(j.type() == json::value_t::boolean);
        }

        SECTION("filled boolean (false)")
        {
            json j(false);
            CHECK(j.type() == json::value_t::boolean);
        }
    }

    SECTION("create an integer number (explicit)")
    {
        SECTION("uninitialized value")
        {
            json::number_integer_t n{};
            json j(n);
            CHECK(j.type() == json::value_t::number_integer);
        }

        SECTION("initialized value")
        {
            json::number_integer_t n(42);
            json j(n);
            CHECK(j.type() == json::value_t::number_integer);
        }
    }

    SECTION("create an integer number (implicit)")
    {
        // reference objects
        json::number_integer_t n_reference = 42;
        json j_reference(n_reference);
        json::number_unsigned_t n_unsigned_reference = 42;
        json j_unsigned_reference(n_unsigned_reference);

        SECTION("short")
        {
            short n = 42;
            json j(n);
            CHECK(j.type() == json::value_t::number_integer);
            CHECK(j == j_reference);
        }

        SECTION("unsigned short")
        {
            unsigned short n = 42;
            json j(n);
            CHECK(j.type() == json::value_t::number_unsigned);
            CHECK(j == j_unsigned_reference);
        }

        SECTION("int")
        {
            int n = 42;
            json j(n);
            CHECK(j.type() == json::value_t::number_integer);
            CHECK(j == j_reference);
        }

        SECTION("unsigned int")
        {
            unsigned int n = 42;
            json j(n);
            CHECK(j.type() == json::value_t::number_unsigned);
            CHECK(j == j_unsigned_reference);
        }

        SECTION("long")
        {
            long n = 42;
            json j(n);
            CHECK(j.type() == json::value_t::number_integer);
            CHECK(j == j_reference);
        }

        SECTION("unsigned long")
        {
            unsigned long n = 42;
            json j(n);
            CHECK(j.type() == json::value_t::number_unsigned);
            CHECK(j == j_unsigned_reference);
        }

        SECTION("long long")
        {
            long long n = 42;
            json j(n);
            CHECK(j.type() == json::value_t::number_integer);
            CHECK(j == j_reference);
        }

        SECTION("unsigned long long")
        {
            unsigned long long n = 42;
            json j(n);
            CHECK(j.type() == json::value_t::number_unsigned);
            CHECK(j == j_unsigned_reference);
        }

        SECTION("int8_t")
        {
            int8_t n = 42;
            json j(n);
            CHECK(j.type() == json::value_t::number_integer);
            CHECK(j == j_reference);
        }

        SECTION("int16_t")
        {
            int16_t n = 42;
            json j(n);
            CHECK(j.type() == json::value_t::number_integer);
            CHECK(j == j_reference);
        }

        SECTION("int32_t")
        {
            int32_t n = 42;
            json j(n);
            CHECK(j.type() == json::value_t::number_integer);
            CHECK(j == j_reference);
        }

        SECTION("int64_t")
        {
            int64_t n = 42;
            json j(n);
            CHECK(j.type() == json::value_t::number_integer);
            CHECK(j == j_reference);
        }

        SECTION("int_fast8_t")
        {
            int_fast8_t n = 42;
            json j(n);
            CHECK(j.type() == json::value_t::number_integer);
            CHECK(j == j_reference);
        }

        SECTION("int_fast16_t")
        {
            int_fast16_t n = 42;
            json j(n);
            CHECK(j.type() == json::value_t::number_integer);
            CHECK(j == j_reference);
        }

        SECTION("int_fast32_t")
        {
            int_fast32_t n = 42;
            json j(n);
            CHECK(j.type() == json::value_t::number_integer);
            CHECK(j == j_reference);
        }

        SECTION("int_fast64_t")
        {
            int_fast64_t n = 42;
            json j(n);
            CHECK(j.type() == json::value_t::number_integer);
            CHECK(j == j_reference);
        }

        SECTION("int_least8_t")
        {
            int_least8_t n = 42;
            json j(n);
            CHECK(j.type() == json::value_t::number_integer);
            CHECK(j == j_reference);
        }

        SECTION("int_least16_t")
        {
            int_least16_t n = 42;
            json j(n);
            CHECK(j.type() == json::value_t::number_integer);
            CHECK(j == j_reference);
        }

        SECTION("int_least32_t")
        {
            int_least32_t n = 42;
            json j(n);
            CHECK(j.type() == json::value_t::number_integer);
            CHECK(j == j_reference);
        }

        SECTION("int_least64_t")
        {
            int_least64_t n = 42;
            json j(n);
            CHECK(j.type() == json::value_t::number_integer);
            CHECK(j == j_reference);
        }

        SECTION("uint8_t")
        {
            uint8_t n = 42;
            json j(n);
            CHECK(j.type() == json::value_t::number_unsigned);
            CHECK(j == j_unsigned_reference);
        }

        SECTION("uint16_t")
        {
            uint16_t n = 42;
            json j(n);
            CHECK(j.type() == json::value_t::number_unsigned);
            CHECK(j == j_unsigned_reference);
        }

        SECTION("uint32_t")
        {
            uint32_t n = 42;
            json j(n);
            CHECK(j.type() == json::value_t::number_unsigned);
            CHECK(j == j_unsigned_reference);
        }

        SECTION("uint64_t")
        {
            uint64_t n = 42;
            json j(n);
            CHECK(j.type() == json::value_t::number_unsigned);
            CHECK(j == j_unsigned_reference);
        }

        SECTION("uint_fast8_t")
        {
            uint_fast8_t n = 42;
            json j(n);
            CHECK(j.type() == json::value_t::number_unsigned);
            CHECK(j == j_unsigned_reference);
        }

        SECTION("uint_fast16_t")
        {
            uint_fast16_t n = 42;
            json j(n);
            CHECK(j.type() == json::value_t::number_unsigned);
            CHECK(j == j_unsigned_reference);
        }

        SECTION("uint_fast32_t")
        {
            uint_fast32_t n = 42;
            json j(n);
            CHECK(j.type() == json::value_t::number_unsigned);
            CHECK(j == j_unsigned_reference);
        }

        SECTION("uint_fast64_t")
        {
            uint_fast64_t n = 42;
            json j(n);
            CHECK(j.type() == json::value_t::number_unsigned);
            CHECK(j == j_unsigned_reference);
        }

        SECTION("uint_least8_t")
        {
            uint_least8_t n = 42;
            json j(n);
            CHECK(j.type() == json::value_t::number_unsigned);
            CHECK(j == j_unsigned_reference);
        }

        SECTION("uint_least16_t")
        {
            uint_least16_t n = 42;
            json j(n);
            CHECK(j.type() == json::value_t::number_unsigned);
            CHECK(j == j_unsigned_reference);
        }

        SECTION("uint_least32_t")
        {
            uint_least32_t n = 42;
            json j(n);
            CHECK(j.type() == json::value_t::number_unsigned);
            CHECK(j == j_unsigned_reference);
        }

        SECTION("uint_least64_t")
        {
            uint_least64_t n = 42;
            json j(n);
            CHECK(j.type() == json::value_t::number_unsigned);
            CHECK(j == j_unsigned_reference);
        }

        SECTION("integer literal without suffix")
        {
            json j(42);
            CHECK(j.type() == json::value_t::number_integer);
            CHECK(j == j_reference);
        }

        SECTION("integer literal with u suffix")
        {
            json j(42u);
            CHECK(j.type() == json::value_t::number_unsigned);
            CHECK(j == j_unsigned_reference);
        }

        SECTION("integer literal with l suffix")
        {
            json j(42l);
            CHECK(j.type() == json::value_t::number_integer);
            CHECK(j == j_reference);
        }

        SECTION("integer literal with ul suffix")
        {
            json j(42ul);
            CHECK(j.type() == json::value_t::number_unsigned);
            CHECK(j == j_unsigned_reference);
        }

        SECTION("integer literal with ll suffix")
        {
            json j(42ll);
            CHECK(j.type() == json::value_t::number_integer);
            CHECK(j == j_reference);
        }

        SECTION("integer literal with ull suffix")
        {
            json j(42ull);
            CHECK(j.type() == json::value_t::number_unsigned);
            CHECK(j == j_unsigned_reference);
        }
    }

    SECTION("create a floating-point number (explicit)")
    {
        SECTION("uninitialized value")
        {
            json::number_float_t n{};
            json j(n);
            CHECK(j.type() == json::value_t::number_float);
        }

        SECTION("initialized value")
        {
            json::number_float_t n(42.23);
            json j(n);
            CHECK(j.type() == json::value_t::number_float);
        }
    }

    SECTION("create a floating-point number (implicit)")
    {
        // reference object
        json::number_float_t n_reference = 42.23;
        json j_reference(n_reference);

        SECTION("float")
        {
            float n = 42.23;
            json j(n);
            CHECK(j.type() == json::value_t::number_float);
            CHECK(j.m_value.number_float == Approx(j_reference.m_value.number_float));
        }

        SECTION("double")
        {
            double n = 42.23;
            json j(n);
            CHECK(j.type() == json::value_t::number_float);
            CHECK(j.m_value.number_float == Approx(j_reference.m_value.number_float));
        }

        SECTION("long double")
        {
            long double n = 42.23;
            json j(n);
            CHECK(j.type() == json::value_t::number_float);
            CHECK(j.m_value.number_float == Approx(j_reference.m_value.number_float));
        }

        SECTION("floating-point literal without suffix")
        {
            json j(42.23);
            CHECK(j.type() == json::value_t::number_float);
            CHECK(j.m_value.number_float == Approx(j_reference.m_value.number_float));
        }

        SECTION("integer literal with f suffix")
        {
            json j(42.23f);
            CHECK(j.type() == json::value_t::number_float);
            CHECK(j.m_value.number_float == Approx(j_reference.m_value.number_float));
        }

        SECTION("integer literal with l suffix")
        {
            json j(42.23l);
            CHECK(j.type() == json::value_t::number_float);
            CHECK(j.m_value.number_float == Approx(j_reference.m_value.number_float));
        }
    }

    SECTION("create a container (array or object) from an initializer list")
    {
        SECTION("empty initializer list")
        {
            SECTION("explicit")
            {
                std::initializer_list<json> l;
                json j(l);
                CHECK(j.type() == json::value_t::object);
            }

            SECTION("implicit")
            {
                json j {};
                CHECK(j.type() == json::value_t::null);
            }
        }

        SECTION("one element")
        {
            SECTION("array")
            {
                SECTION("explicit")
                {
                    std::initializer_list<json> l = {json(json::array_t())};
                    json j(l);
                    CHECK(j.type() == json::value_t::array);
                }

                SECTION("implicit")
                {
                    json j {json::array_t()};
                    CHECK(j.type() == json::value_t::array);
                }
            }

            SECTION("object")
            {
                SECTION("explicit")
                {
                    std::initializer_list<json> l = {json(json::object_t())};
                    json j(l);
                    CHECK(j.type() == json::value_t::array);
                }

                SECTION("implicit")
                {
                    json j {json::object_t()};
                    CHECK(j.type() == json::value_t::array);
                }
            }

            SECTION("string")
            {
                SECTION("explicit")
                {
                    std::initializer_list<json> l = {json("Hello world")};
                    json j(l);
                    CHECK(j.type() == json::value_t::array);
                }

                SECTION("implicit")
                {
                    json j {"Hello world"};
                    CHECK(j.type() == json::value_t::array);
                }
            }

            SECTION("boolean")
            {
                SECTION("explicit")
                {
                    std::initializer_list<json> l = {json(true)};
                    json j(l);
                    CHECK(j.type() == json::value_t::array);
                }

                SECTION("implicit")
                {
                    json j {true};
                    CHECK(j.type() == json::value_t::array);
                }
            }

            SECTION("number (integer)")
            {
                SECTION("explicit")
                {
                    std::initializer_list<json> l = {json(1)};
                    json j(l);
                    CHECK(j.type() == json::value_t::array);
                }

                SECTION("implicit")
                {
                    json j {1};
                    CHECK(j.type() == json::value_t::array);
                }
            }

            SECTION("number (unsigned)")
            {
                SECTION("explicit")
                {
                    std::initializer_list<json> l = {json(1u)};
                    json j(l);
                    CHECK(j.type() == json::value_t::array);
                }

                SECTION("implicit")
                {
                    json j {1u};
                    CHECK(j.type() == json::value_t::array);
                }
            }

            SECTION("number (floating-point)")
            {
                SECTION("explicit")
                {
                    std::initializer_list<json> l = {json(42.23)};
                    json j(l);
                    CHECK(j.type() == json::value_t::array);
                }

                SECTION("implicit")
                {
                    json j {42.23};
                    CHECK(j.type() == json::value_t::array);
                }
            }
        }

        SECTION("more elements")
        {
            SECTION("explicit")
            {
                std::initializer_list<json> l = {1, 1u, 42.23, true, nullptr, json::object_t(), json::array_t()};
                json j(l);
                CHECK(j.type() == json::value_t::array);
            }

            SECTION("implicit")
            {
                json j {1, 1u, 42.23, true, nullptr, json::object_t(), json::array_t()};
                CHECK(j.type() == json::value_t::array);
            }
        }

        SECTION("implicit type deduction")
        {
            SECTION("object")
            {
                json j { {"one", 1}, {"two", 1u}, {"three", 2.2}, {"four", false} };
                CHECK(j.type() == json::value_t::object);
            }

            SECTION("array")
            {
                json j { {"one", 1}, {"two", 1u}, {"three", 2.2}, {"four", false} , 13 };
                CHECK(j.type() == json::value_t::array);
            }
        }

        SECTION("explicit type deduction")
        {
            SECTION("empty object")
            {
                json j = json::object();
                CHECK(j.type() == json::value_t::object);
            }

            SECTION("object")
            {
                json j = json::object({ {"one", 1}, {"two", 1u}, {"three", 2.2}, {"four", false} });
                CHECK(j.type() == json::value_t::object);
            }

            SECTION("object with error")
            {
                CHECK_THROWS_AS(json::object({ {"one", 1}, {"two", 1u}, {"three", 2.2}, {"four", false}, 13 }),
                std::logic_error);
                CHECK_THROWS_WITH(json::object({ {"one", 1}, {"two", 1u}, {"three", 2.2}, {"four", false}, 13 }),
                "cannot create object from initializer list");
            }

            SECTION("empty array")
            {
                json j = json::array();
                CHECK(j.type() == json::value_t::array);
            }

            SECTION("array")
            {
                json j = json::array({ {"one", 1}, {"two", 1u}, {"three", 2.2}, {"four", false} });
                CHECK(j.type() == json::value_t::array);
            }
        }
    }

    SECTION("create an array of n copies of a given value")
    {
        json v = {1, "foo", 34.23, {1, 2, 3}, {{"A", 1}, {"B", 2u}}};
        json arr(3, v);
        CHECK(arr.size() == 3);
        for (auto& x : arr)
        {
            CHECK(x == v);
        }
    }

    SECTION("create a JSON container from an iterator range")
    {
        SECTION("object")
        {
            SECTION("json(begin(), end())")
            {
                {
                    json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
                    json j_new(jobject.begin(), jobject.end());
                    CHECK(j_new == jobject);
                }
                {
                    json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
                    json j_new(jobject.cbegin(), jobject.cend());
                    CHECK(j_new == jobject);
                }
            }

            SECTION("json(begin(), begin())")
            {
                {
                    json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
                    json j_new(jobject.begin(), jobject.begin());
                    CHECK(j_new == json::object());
                }
                {
                    json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
                    json j_new(jobject.cbegin(), jobject.cbegin());
                    CHECK(j_new == json::object());
                }
            }

            SECTION("construct from subrange")
            {
                json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}, {"d", false}, {"e", true}};
                json j_new(jobject.find("b"), jobject.find("e"));
                CHECK(j_new == json({{"b", 1}, {"c", 17u}, {"d", false}}));
            }

            SECTION("incompatible iterators")
            {
                {
                    json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}, {"d", false}, {"e", true}};
                    json jobject2 = {{"a", "a"}, {"b", 1}, {"c", 17u}};
                    CHECK_THROWS_AS(json(jobject.begin(), jobject2.end()), std::domain_error);
                    CHECK_THROWS_AS(json(jobject2.begin(), jobject.end()), std::domain_error);
                    CHECK_THROWS_WITH(json(jobject.begin(), jobject2.end()), "iterators are not compatible");
                    CHECK_THROWS_WITH(json(jobject2.begin(), jobject.end()), "iterators are not compatible");
                }
                {
                    json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}, {"d", false}, {"e", true}};
                    json jobject2 = {{"a", "a"}, {"b", 1}, {"c", 17u}};
                    CHECK_THROWS_AS(json(jobject.cbegin(), jobject2.cend()), std::domain_error);
                    CHECK_THROWS_AS(json(jobject2.cbegin(), jobject.cend()), std::domain_error);
                    CHECK_THROWS_WITH(json(jobject.cbegin(), jobject2.cend()), "iterators are not compatible");
                    CHECK_THROWS_WITH(json(jobject2.cbegin(), jobject.cend()), "iterators are not compatible");
                }
            }
        }

        SECTION("array")
        {
            SECTION("json(begin(), end())")
            {
                {
                    json jarray = {1, 2, 3, 4, 5};
                    json j_new(jarray.begin(), jarray.end());
                    CHECK(j_new == jarray);
                }
                {
                    json jarray = {1, 2, 3, 4, 5};
                    json j_new(jarray.cbegin(), jarray.cend());
                    CHECK(j_new == jarray);
                }
            }

            SECTION("json(begin(), begin())")
            {
                {
                    json jarray = {1, 2, 3, 4, 5};
                    json j_new(jarray.begin(), jarray.begin());
                    CHECK(j_new == json::array());
                }
                {
                    json jarray = {1, 2, 3, 4, 5};
                    json j_new(jarray.cbegin(), jarray.cbegin());
                    CHECK(j_new == json::array());
                }
            }

            SECTION("construct from subrange")
            {
                {
                    json jarray = {1, 2, 3, 4, 5};
                    json j_new(jarray.begin() + 1, jarray.begin() + 3);
                    CHECK(j_new == json({2, 3}));
                }
                {
                    json jarray = {1, 2, 3, 4, 5};
                    json j_new(jarray.cbegin() + 1, jarray.cbegin() + 3);
                    CHECK(j_new == json({2, 3}));
                }
            }

            SECTION("incompatible iterators")
            {
                {
                    json jarray = {1, 2, 3, 4};
                    json jarray2 = {2, 3, 4, 5};
                    CHECK_THROWS_AS(json(jarray.begin(), jarray2.end()), std::domain_error);
                    CHECK_THROWS_AS(json(jarray2.begin(), jarray.end()), std::domain_error);
                    CHECK_THROWS_WITH(json(jarray.begin(), jarray2.end()), "iterators are not compatible");
                    CHECK_THROWS_WITH(json(jarray2.begin(), jarray.end()), "iterators are not compatible");
                }
                {
                    json jarray = {1, 2, 3, 4};
                    json jarray2 = {2, 3, 4, 5};
                    CHECK_THROWS_AS(json(jarray.cbegin(), jarray2.cend()), std::domain_error);
                    CHECK_THROWS_AS(json(jarray2.cbegin(), jarray.cend()), std::domain_error);
                    CHECK_THROWS_WITH(json(jarray.cbegin(), jarray2.cend()), "iterators are not compatible");
                    CHECK_THROWS_WITH(json(jarray2.cbegin(), jarray.cend()), "iterators are not compatible");
                }
            }
        }

        SECTION("other values")
        {
            SECTION("construct with two valid iterators")
            {
                SECTION("null")
                {
                    {
                        json j;
                        CHECK_THROWS_AS(json(j.begin(), j.end()), std::domain_error);
                        CHECK_THROWS_WITH(json(j.begin(), j.end()), "cannot use construct with iterators from null");
                    }
                    {
                        json j;
                        CHECK_THROWS_AS(json(j.cbegin(), j.cend()), std::domain_error);
                        CHECK_THROWS_WITH(json(j.cbegin(), j.cend()), "cannot use construct with iterators from null");
                    }
                }

                SECTION("string")
                {
                    {
                        json j = "foo";
                        json j_new(j.begin(), j.end());
                        CHECK(j == j_new);
                    }
                    {
                        json j = "bar";
                        json j_new(j.cbegin(), j.cend());
                        CHECK(j == j_new);
                    }
                }

                SECTION("number (boolean)")
                {
                    {
                        json j = false;
                        json j_new(j.begin(), j.end());
                        CHECK(j == j_new);
                    }
                    {
                        json j = true;
                        json j_new(j.cbegin(), j.cend());
                        CHECK(j == j_new);
                    }
                }

                SECTION("number (integer)")
                {
                    {
                        json j = 17;
                        json j_new(j.begin(), j.end());
                        CHECK(j == j_new);
                    }
                    {
                        json j = 17;
                        json j_new(j.cbegin(), j.cend());
                        CHECK(j == j_new);
                    }
                }

                SECTION("number (unsigned)")
                {
                    {
                        json j = 17u;
                        json j_new(j.begin(), j.end());
                        CHECK(j == j_new);
                    }
                    {
                        json j = 17u;
                        json j_new(j.cbegin(), j.cend());
                        CHECK(j == j_new);
                    }
                }

                SECTION("number (floating point)")
                {
                    {
                        json j = 23.42;
                        json j_new(j.begin(), j.end());
                        CHECK(j == j_new);
                    }
                    {
                        json j = 23.42;
                        json j_new(j.cbegin(), j.cend());
                        CHECK(j == j_new);
                    }
                }
            }

            SECTION("construct with two invalid iterators")
            {
                SECTION("string")
                {
                    {
                        json j = "foo";
                        CHECK_THROWS_AS(json(j.end(), j.end()), std::out_of_range);
                        CHECK_THROWS_AS(json(j.begin(), j.begin()), std::out_of_range);
                        CHECK_THROWS_WITH(json(j.end(), j.end()), "iterators out of range");
                        CHECK_THROWS_WITH(json(j.begin(), j.begin()), "iterators out of range");
                    }
                    {
                        json j = "bar";
                        CHECK_THROWS_AS(json(j.cend(), j.cend()), std::out_of_range);
                        CHECK_THROWS_AS(json(j.cbegin(), j.cbegin()), std::out_of_range);
                        CHECK_THROWS_WITH(json(j.cend(), j.cend()), "iterators out of range");
                        CHECK_THROWS_WITH(json(j.cbegin(), j.cbegin()), "iterators out of range");
                    }
                }

                SECTION("number (boolean)")
                {
                    {
                        json j = false;
                        CHECK_THROWS_AS(json(j.end(), j.end()), std::out_of_range);
                        CHECK_THROWS_AS(json(j.begin(), j.begin()), std::out_of_range);
                        CHECK_THROWS_WITH(json(j.end(), j.end()), "iterators out of range");
                        CHECK_THROWS_WITH(json(j.begin(), j.begin()), "iterators out of range");
                    }
                    {
                        json j = true;
                        CHECK_THROWS_AS(json(j.cend(), j.cend()), std::out_of_range);
                        CHECK_THROWS_AS(json(j.cbegin(), j.cbegin()), std::out_of_range);
                        CHECK_THROWS_WITH(json(j.cend(), j.cend()), "iterators out of range");
                        CHECK_THROWS_WITH(json(j.cbegin(), j.cbegin()), "iterators out of range");
                    }
                }

                SECTION("number (integer)")
                {
                    {
                        json j = 17;
                        CHECK_THROWS_AS(json(j.end(), j.end()), std::out_of_range);
                        CHECK_THROWS_AS(json(j.begin(), j.begin()), std::out_of_range);
                        CHECK_THROWS_WITH(json(j.end(), j.end()), "iterators out of range");
                        CHECK_THROWS_WITH(json(j.begin(), j.begin()), "iterators out of range");
                    }
                    {
                        json j = 17;
                        CHECK_THROWS_AS(json(j.cend(), j.cend()), std::out_of_range);
                        CHECK_THROWS_AS(json(j.cbegin(), j.cbegin()), std::out_of_range);
                        CHECK_THROWS_WITH(json(j.cend(), j.cend()), "iterators out of range");
                        CHECK_THROWS_WITH(json(j.cbegin(), j.cbegin()), "iterators out of range");
                    }
                }

                SECTION("number (integer)")
                {
                    {
                        json j = 17u;
                        CHECK_THROWS_AS(json(j.end(), j.end()), std::out_of_range);
                        CHECK_THROWS_AS(json(j.begin(), j.begin()), std::out_of_range);
                        CHECK_THROWS_WITH(json(j.end(), j.end()), "iterators out of range");
                        CHECK_THROWS_WITH(json(j.begin(), j.begin()), "iterators out of range");
                    }
                    {
                        json j = 17u;
                        CHECK_THROWS_AS(json(j.cend(), j.cend()), std::out_of_range);
                        CHECK_THROWS_AS(json(j.cbegin(), j.cbegin()), std::out_of_range);
                        CHECK_THROWS_WITH(json(j.cend(), j.cend()), "iterators out of range");
                        CHECK_THROWS_WITH(json(j.cbegin(), j.cbegin()), "iterators out of range");
                    }
                }

                SECTION("number (floating point)")
                {
                    {
                        json j = 23.42;
                        CHECK_THROWS_AS(json(j.end(), j.end()), std::out_of_range);
                        CHECK_THROWS_AS(json(j.begin(), j.begin()), std::out_of_range);
                        CHECK_THROWS_WITH(json(j.end(), j.end()), "iterators out of range");
                        CHECK_THROWS_WITH(json(j.begin(), j.begin()), "iterators out of range");
                    }
                    {
                        json j = 23.42;
                        CHECK_THROWS_AS(json(j.cend(), j.cend()), std::out_of_range);
                        CHECK_THROWS_AS(json(j.cbegin(), j.cbegin()), std::out_of_range);
                        CHECK_THROWS_WITH(json(j.cend(), j.cend()), "iterators out of range");
                        CHECK_THROWS_WITH(json(j.cbegin(), j.cbegin()), "iterators out of range");
                    }
                }
            }
        }
    }

    SECTION("create a JSON value from an input stream")
    {
        SECTION("std::stringstream")
        {
            std::stringstream ss;
            ss << "[\"foo\",1,2,3,false,{\"one\":1}]";
            json j(ss);
            CHECK(j == json({"foo", 1, 2, 3, false, {{"one", 1}}}));
        }

        SECTION("with callback function")
        {
            std::stringstream ss;
            ss << "[\"foo\",1,2,3,false,{\"one\":1}]";
            json j(ss, [](int, json::parse_event_t, const json & val)
            {
                // filter all number(2) elements
                if (val == json(2))
                {
                    return false;
                }
                else
                {
                    return true;
                }
            });
            CHECK(j == json({"foo", 1, 3, false, {{"one", 1}}}));
        }

        SECTION("std::ifstream")
        {
            std::ifstream f("test/data/json_tests/pass1.json");
            json j(f);
        }
    }
}

TEST_CASE("other constructors and destructor")
{
    SECTION("copy constructor")
    {
        SECTION("object")
        {
            json j {{"foo", 1}, {"bar", false}};
            json k(j);
            CHECK(j == k);
        }

        SECTION("array")
        {
            json j {"foo", 1, 42.23, false};
            json k(j);
            CHECK(j == k);
        }

        SECTION("null")
        {
            json j(nullptr);
            json k(j);
            CHECK(j == k);
        }

        SECTION("boolean")
        {
            json j(true);
            json k(j);
            CHECK(j == k);
        }

        SECTION("string")
        {
            json j("Hello world");
            json k(j);
            CHECK(j == k);
        }

        SECTION("number (integer)")
        {
            json j(42);
            json k(j);
            CHECK(j == k);
        }

        SECTION("number (unsigned)")
        {
            json j(42u);
            json k(j);
            CHECK(j == k);
        }

        SECTION("number (floating-point)")
        {
            json j(42.23);
            json k(j);
            CHECK(j == k);
        }
    }

    SECTION("move constructor")
    {
        json j {{"foo", "bar"}, {"baz", {1, 2, 3, 4}}, {"a", 42u}, {"b", 42.23}, {"c", nullptr}};
        CHECK(j.type() == json::value_t::object);
        json k(std::move(j));
        CHECK(k.type() == json::value_t::object);
        CHECK(j.type() == json::value_t::null);
    }

    SECTION("copy assignment")
    {
        SECTION("object")
        {
            json j {{"foo", 1}, {"bar", false}};
            json k;
            k = j;
            CHECK(j == k);
        }

        SECTION("array")
        {
            json j {"foo", 1, 42.23, false};
            json k;
            k = j;
            CHECK(j == k);
        }

        SECTION("null")
        {
            json j(nullptr);
            json k;
            k = j;
            CHECK(j == k);
        }

        SECTION("boolean")
        {
            json j(true);
            json k;
            k = j;
            CHECK(j == k);
        }

        SECTION("string")
        {
            json j("Hello world");
            json k;
            k = j;
            CHECK(j == k);
        }

        SECTION("number (integer)")
        {
            json j(42);
            json k;
            k = j;
            CHECK(j == k);
        }

        SECTION("number (unsigned)")
        {
            json j(42u);
            json k;
            k = j;
            CHECK(j == k);
        }

        SECTION("number (floating-point)")
        {
            json j(42.23);
            json k;
            k = j;
            CHECK(j == k);
        }
    }

    SECTION("destructor")
    {
        SECTION("object")
        {
            auto j = new json {{"foo", 1}, {"bar", false}};
            delete j;
        }

        SECTION("array")
        {
            auto j = new json {"foo", 1, 1u, false, 23.42};
            delete j;
        }

        SECTION("string")
        {
            auto j = new json("Hello world");
            delete j;
        }
    }
}

TEST_CASE("object inspection")
{
    SECTION("convenience type checker")
    {
        SECTION("object")
        {
            json j {{"foo", 1}, {"bar", false}};
            CHECK(not j.is_null());
            CHECK(not j.is_boolean());
            CHECK(not j.is_number());
            CHECK(not j.is_number_integer());
            CHECK(not j.is_number_unsigned());
            CHECK(not j.is_number_float());
            CHECK(j.is_object());
            CHECK(not j.is_array());
            CHECK(not j.is_string());
            CHECK(not j.is_discarded());
            CHECK(not j.is_primitive());
            CHECK(j.is_structured());
        }

        SECTION("array")
        {
            json j {"foo", 1, 1u, 42.23, false};
            CHECK(not j.is_null());
            CHECK(not j.is_boolean());
            CHECK(not j.is_number());
            CHECK(not j.is_number_integer());
            CHECK(not j.is_number_unsigned());
            CHECK(not j.is_number_float());
            CHECK(not j.is_object());
            CHECK(j.is_array());
            CHECK(not j.is_string());
            CHECK(not j.is_discarded());
            CHECK(not j.is_primitive());
            CHECK(j.is_structured());
        }

        SECTION("null")
        {
            json j(nullptr);
            CHECK(j.is_null());
            CHECK(not j.is_boolean());
            CHECK(not j.is_number());
            CHECK(not j.is_number_integer());
            CHECK(not j.is_number_unsigned());
            CHECK(not j.is_number_float());
            CHECK(not j.is_object());
            CHECK(not j.is_array());
            CHECK(not j.is_string());
            CHECK(not j.is_discarded());
            CHECK(j.is_primitive());
            CHECK(not j.is_structured());
        }

        SECTION("boolean")
        {
            json j(true);
            CHECK(not j.is_null());
            CHECK(j.is_boolean());
            CHECK(not j.is_number());
            CHECK(not j.is_number_integer());
            CHECK(not j.is_number_unsigned());
            CHECK(not j.is_number_float());
            CHECK(not j.is_object());
            CHECK(not j.is_array());
            CHECK(not j.is_string());
            CHECK(not j.is_discarded());
            CHECK(j.is_primitive());
            CHECK(not j.is_structured());
        }

        SECTION("string")
        {
            json j("Hello world");
            CHECK(not j.is_null());
            CHECK(not j.is_boolean());
            CHECK(not j.is_number());
            CHECK(not j.is_number_integer());
            CHECK(not j.is_number_unsigned());
            CHECK(not j.is_number_float());
            CHECK(not j.is_object());
            CHECK(not j.is_array());
            CHECK(j.is_string());
            CHECK(not j.is_discarded());
            CHECK(j.is_primitive());
            CHECK(not j.is_structured());
        }

        SECTION("number (integer)")
        {
            json j(42);
            CHECK(not j.is_null());
            CHECK(not j.is_boolean());
            CHECK(j.is_number());
            CHECK(j.is_number_integer());
            CHECK(not j.is_number_unsigned());
            CHECK(not j.is_number_float());
            CHECK(not j.is_object());
            CHECK(not j.is_array());
            CHECK(not j.is_string());
            CHECK(not j.is_discarded());
            CHECK(j.is_primitive());
            CHECK(not j.is_structured());
        }

        SECTION("number (unsigned)")
        {
            json j(42u);
            CHECK(not j.is_null());
            CHECK(not j.is_boolean());
            CHECK(j.is_number());
            CHECK(j.is_number_integer());
            CHECK(j.is_number_unsigned());
            CHECK(not j.is_number_float());
            CHECK(not j.is_object());
            CHECK(not j.is_array());
            CHECK(not j.is_string());
            CHECK(not j.is_discarded());
            CHECK(j.is_primitive());
            CHECK(not j.is_structured());
        }

        SECTION("number (floating-point)")
        {
            json j(42.23);
            CHECK(not j.is_null());
            CHECK(not j.is_boolean());
            CHECK(j.is_number());
            CHECK(not j.is_number_integer());
            CHECK(not j.is_number_unsigned());
            CHECK(j.is_number_float());
            CHECK(not j.is_object());
            CHECK(not j.is_array());
            CHECK(not j.is_string());
            CHECK(not j.is_discarded());
            CHECK(j.is_primitive());
            CHECK(not j.is_structured());
        }

        SECTION("discarded")
        {
            json j(json::value_t::discarded);
            CHECK(not j.is_null());
            CHECK(not j.is_boolean());
            CHECK(not j.is_number());
            CHECK(not j.is_number_integer());
            CHECK(not j.is_number_unsigned());
            CHECK(not j.is_number_float());
            CHECK(not j.is_object());
            CHECK(not j.is_array());
            CHECK(not j.is_string());
            CHECK(j.is_discarded());
            CHECK(not j.is_primitive());
            CHECK(not j.is_structured());
        }
    }

    SECTION("serialization")
    {
        json j {{"object", json::object()}, {"array", {1, 2, 3, 4}}, {"number", 42}, {"boolean", false}, {"null", nullptr}, {"string", "Hello world"} };

        SECTION("no indent / indent=-1")
        {
            CHECK(j.dump() ==
                  "{\"array\":[1,2,3,4],\"boolean\":false,\"null\":null,\"number\":42,\"object\":{},\"string\":\"Hello world\"}");

            CHECK(j.dump() == j.dump(-1));
        }

        SECTION("indent=0")
        {
            CHECK(j.dump(0) ==
                  "{\n\"array\": [\n1,\n2,\n3,\n4\n],\n\"boolean\": false,\n\"null\": null,\n\"number\": 42,\n\"object\": {},\n\"string\": \"Hello world\"\n}");
        }

        SECTION("indent=4")
        {
            CHECK(j.dump(4) ==
                  "{\n    \"array\": [\n        1,\n        2,\n        3,\n        4\n    ],\n    \"boolean\": false,\n    \"null\": null,\n    \"number\": 42,\n    \"object\": {},\n    \"string\": \"Hello world\"\n}");
        }

        SECTION("dump and floating-point numbers")
        {
            auto s = json(42.23).dump();
            CHECK(s.find("42.23") != std::string::npos);
        }

        SECTION("dump and small floating-point numbers")
        {
            auto s = json(1.23456e-78).dump();
            CHECK(s.find("1.23456e-78") != std::string::npos);
        }

        SECTION("dump and non-ASCII characters")
        {
            CHECK(json("ä").dump() == "\"ä\"");
            CHECK(json("Ö").dump() == "\"Ö\"");
            CHECK(json("❤️").dump() == "\"❤️\"");
        }

        SECTION("serialization of discarded element")
        {
            json j_discarded(json::value_t::discarded);
            CHECK(j_discarded.dump() == "<discarded>");
        }
    }

    SECTION("return the type of the object (explicit)")
    {
        SECTION("null")
        {
            json j = nullptr;
            CHECK(j.type() == json::value_t::null);
        }

        SECTION("object")
        {
            json j = {{"foo", "bar"}};
            CHECK(j.type() == json::value_t::object);
        }

        SECTION("array")
        {
            json j = {1, 2, 3, 4};
            CHECK(j.type() == json::value_t::array);
        }

        SECTION("boolean")
        {
            json j = true;
            CHECK(j.type() == json::value_t::boolean);
        }

        SECTION("string")
        {
            json j = "Hello world";
            CHECK(j.type() == json::value_t::string);
        }

        SECTION("number (integer)")
        {
            json j = 23;
            CHECK(j.type() == json::value_t::number_integer);
        }

        SECTION("number (unsigned)")
        {
            json j = 23u;
            CHECK(j.type() == json::value_t::number_unsigned);
        }

        SECTION("number (floating-point)")
        {
            json j = 42.23;
            CHECK(j.type() == json::value_t::number_float);
        }
    }

    SECTION("return the type of the object (implicit)")
    {
        SECTION("null")
        {
            json j = nullptr;
            json::value_t t = j;
            CHECK(t == j.type());
        }

        SECTION("object")
        {
            json j = {{"foo", "bar"}};
            json::value_t t = j;
            CHECK(t == j.type());
        }

        SECTION("array")
        {
            json j = {1, 2, 3, 4};
            json::value_t t = j;
            CHECK(t == j.type());
        }

        SECTION("boolean")
        {
            json j = true;
            json::value_t t = j;
            CHECK(t == j.type());
        }

        SECTION("string")
        {
            json j = "Hello world";
            json::value_t t = j;
            CHECK(t == j.type());
        }

        SECTION("number (integer)")
        {
            json j = 23;
            json::value_t t = j;
            CHECK(t == j.type());
        }

        SECTION("number (unsigned)")
        {
            json j = 23u;
            json::value_t t = j;
            CHECK(t == j.type());
        }

        SECTION("number (floating-point)")
        {
            json j = 42.23;
            json::value_t t = j;
            CHECK(t == j.type());
        }
    }
}

TEST_CASE("value conversion")
{
    SECTION("get an object (explicit)")
    {
        json::object_t o_reference = {{"object", json::object()}, {"array", {1, 2, 3, 4}}, {"number", 42}, {"boolean", false}, {"null", nullptr}, {"string", "Hello world"} };
        json j(o_reference);

        SECTION("json::object_t")
        {
            json::object_t o = j.get<json::object_t>();
            CHECK(json(o) == j);
        }

        SECTION("std::map<json::string_t, json>")
        {
            std::map<json::string_t, json> o = j.get<std::map<json::string_t, json>>();
            CHECK(json(o) == j);
        }

        SECTION("std::multimap<json::string_t, json>")
        {
            std::multimap<json::string_t, json> o = j.get<std::multimap<json::string_t, json>>();
            CHECK(json(o) == j);
        }

        SECTION("std::unordered_map<json::string_t, json>")
        {
            std::unordered_map<json::string_t, json> o = j.get<std::unordered_map<json::string_t, json>>();
            CHECK(json(o) == j);
        }

        SECTION("std::unordered_multimap<json::string_t, json>")
        {
            std::unordered_multimap<json::string_t, json> o =
                j.get<std::unordered_multimap<json::string_t, json>>();
            CHECK(json(o) == j);
        }

        SECTION("exception in case of a non-object type")
        {
            CHECK_THROWS_AS(json(json::value_t::null).get<json::object_t>(), std::logic_error);
            CHECK_THROWS_AS(json(json::value_t::array).get<json::object_t>(), std::logic_error);
            CHECK_THROWS_AS(json(json::value_t::string).get<json::object_t>(), std::logic_error);
            CHECK_THROWS_AS(json(json::value_t::boolean).get<json::object_t>(), std::logic_error);
            CHECK_THROWS_AS(json(json::value_t::number_integer).get<json::object_t>(), std::logic_error);
            CHECK_THROWS_AS(json(json::value_t::number_unsigned).get<json::object_t>(), std::logic_error);
            CHECK_THROWS_AS(json(json::value_t::number_float).get<json::object_t>(), std::logic_error);

            CHECK_THROWS_WITH(json(json::value_t::null).get<json::object_t>(),
                              "type must be object, but is null");
            CHECK_THROWS_WITH(json(json::value_t::array).get<json::object_t>(),
                              "type must be object, but is array");
            CHECK_THROWS_WITH(json(json::value_t::string).get<json::object_t>(),
                              "type must be object, but is string");
            CHECK_THROWS_WITH(json(json::value_t::boolean).get<json::object_t>(),
                              "type must be object, but is boolean");
            CHECK_THROWS_WITH(json(json::value_t::number_integer).get<json::object_t>(),
                              "type must be object, but is number");
            CHECK_THROWS_WITH(json(json::value_t::number_unsigned).get<json::object_t>(),
                              "type must be object, but is number");
            CHECK_THROWS_WITH(json(json::value_t::number_float).get<json::object_t>(),
                              "type must be object, but is number");
        }
    }

    SECTION("get an object (implicit)")
    {
        json::object_t o_reference = {{"object", json::object()}, {"array", {1, 2, 3, 4}}, {"number", 42}, {"boolean", false}, {"null", nullptr}, {"string", "Hello world"} };
        json j(o_reference);

        SECTION("json::object_t")
        {
            json::object_t o = j;
            CHECK(json(o) == j);
        }

        SECTION("std::map<json::string_t, json>")
        {
            std::map<json::string_t, json> o = j;
            CHECK(json(o) == j);
        }

        SECTION("std::multimap<json::string_t, json>")
        {
            std::multimap<json::string_t, json> o = j;
            CHECK(json(o) == j);
        }

        SECTION("std::unordered_map<json::string_t, json>")
        {
            std::unordered_map<json::string_t, json> o = j;
            CHECK(json(o) == j);
        }

        SECTION("std::unordered_multimap<json::string_t, json>")
        {
            std::unordered_multimap<json::string_t, json> o = j;
            CHECK(json(o) == j);
        }
    }

    SECTION("get an array (explicit)")
    {
        json::array_t a_reference {json(1), json(1u), json(2.2), json(false), json("string"), json()};
        json j(a_reference);

        SECTION("json::array_t")
        {
            json::array_t a = j.get<json::array_t>();
            CHECK(json(a) == j);
        }

        SECTION("std::list<json>")
        {
            std::list<json> a = j.get<std::list<json>>();
            CHECK(json(a) == j);
        }

        SECTION("std::forward_list<json>")
        {
            std::forward_list<json> a = j.get<std::forward_list<json>>();
            CHECK(json(a) == j);
        }

        SECTION("std::vector<json>")
        {
            std::vector<json> a = j.get<std::vector<json>>();
            CHECK(json(a) == j);
        }

        SECTION("std::deque<json>")
        {
            std::deque<json> a = j.get<std::deque<json>>();
            CHECK(json(a) == j);
        }

        SECTION("exception in case of a non-array type")
        {
            CHECK_THROWS_AS(json(json::value_t::null).get<json::array_t>(), std::logic_error);
            CHECK_THROWS_AS(json(json::value_t::object).get<json::array_t>(), std::logic_error);
            CHECK_THROWS_AS(json(json::value_t::string).get<json::array_t>(), std::logic_error);
            CHECK_THROWS_AS(json(json::value_t::boolean).get<json::array_t>(), std::logic_error);
            CHECK_THROWS_AS(json(json::value_t::number_integer).get<json::array_t>(), std::logic_error);
            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::null).get<json::array_t>(),
                              "type must be array, but is null");
            CHECK_THROWS_WITH(json(json::value_t::object).get<json::array_t>(),
                              "type must be array, but is object");
            CHECK_THROWS_WITH(json(json::value_t::string).get<json::array_t>(),
                              "type must be array, but is string");
            CHECK_THROWS_WITH(json(json::value_t::boolean).get<json::array_t>(),
                              "type must be array, but is boolean");
            CHECK_THROWS_WITH(json(json::value_t::number_integer).get<json::array_t>(),
                              "type must be array, but is number");
            CHECK_THROWS_WITH(json(json::value_t::number_unsigned).get<json::array_t>(),
                              "type must be array, but is number");
            CHECK_THROWS_WITH(json(json::value_t::number_float).get<json::array_t>(),
                              "type must be array, but is number");
        }
    }

    SECTION("get an array (implicit)")
    {
        json::array_t a_reference {json(1), json(1u), json(2.2), json(false), json("string"), json()};
        json j(a_reference);

        SECTION("json::array_t")
        {
            json::array_t a = j;
            CHECK(json(a) == j);
        }

        SECTION("std::list<json>")
        {
            std::list<json> a = j;
            CHECK(json(a) == j);
        }

        SECTION("std::forward_list<json>")
        {
            std::forward_list<json> a = j;
            CHECK(json(a) == j);
        }

        SECTION("std::vector<json>")
        {
            std::vector<json> a = j;
            CHECK(json(a) == j);
        }

        SECTION("std::deque<json>")
        {
            std::deque<json> a = j;
            CHECK(json(a) == j);
        }
    }

    SECTION("get a string (explicit)")
    {
        json::string_t s_reference {"Hello world"};
        json j(s_reference);

        SECTION("string_t")
        {
            json::string_t s = j.get<json::string_t>();
            CHECK(json(s) == j);
        }

        SECTION("std::string")
        {
            std::string s = j.get<std::string>();
            CHECK(json(s) == j);
        }

        SECTION("exception in case of a non-string type")
        {
            CHECK_THROWS_AS(json(json::value_t::null).get<json::string_t>(), std::logic_error);
            CHECK_THROWS_AS(json(json::value_t::object).get<json::string_t>(), std::logic_error);
            CHECK_THROWS_AS(json(json::value_t::array).get<json::string_t>(), std::logic_error);
            CHECK_THROWS_AS(json(json::value_t::boolean).get<json::string_t>(), std::logic_error);
            CHECK_THROWS_AS(json(json::value_t::number_integer).get<json::string_t>(), std::logic_error);
            CHECK_THROWS_AS(json(json::value_t::number_unsigned).get<json::string_t>(), std::logic_error);
            CHECK_THROWS_AS(json(json::value_t::number_float).get<json::string_t>(), std::logic_error);

            CHECK_THROWS_WITH(json(json::value_t::null).get<json::string_t>(),
                              "type must be string, but is null");
            CHECK_THROWS_WITH(json(json::value_t::object).get<json::string_t>(),
                              "type must be string, but is object");
            CHECK_THROWS_WITH(json(json::value_t::array).get<json::string_t>(),
                              "type must be string, but is array");
            CHECK_THROWS_WITH(json(json::value_t::boolean).get<json::string_t>(),
                              "type must be string, but is boolean");
            CHECK_THROWS_WITH(json(json::value_t::number_integer).get<json::string_t>(),
                              "type must be string, but is number");
            CHECK_THROWS_WITH(json(json::value_t::number_unsigned).get<json::string_t>(),
                              "type must be string, but is number");
            CHECK_THROWS_WITH(json(json::value_t::number_float).get<json::string_t>(),
                              "type must be string, but is number");
        }
    }

    SECTION("get a string (implicit)")
    {
        json::string_t s_reference {"Hello world"};
        json j(s_reference);

        SECTION("string_t")
        {
            json::string_t s = j;
            CHECK(json(s) == j);
        }

        SECTION("std::string")
        {
            std::string s = j;
            CHECK(json(s) == j);
        }
    }

    SECTION("get a boolean (explicit)")
    {
        json::boolean_t b_reference {true};
        json j(b_reference);

        SECTION("boolean_t")
        {
            json::boolean_t b = j.get<json::boolean_t>();
            CHECK(json(b) == j);
        }

        SECTION("bool")
        {
            bool b = j.get<bool>();
            CHECK(json(b) == j);
        }

        SECTION("exception in case of a non-string type")
        {
            CHECK_THROWS_AS(json(json::value_t::null).get<json::boolean_t>(), std::logic_error);
            CHECK_THROWS_AS(json(json::value_t::object).get<json::boolean_t>(), std::logic_error);
            CHECK_THROWS_AS(json(json::value_t::array).get<json::boolean_t>(), std::logic_error);
            CHECK_THROWS_AS(json(json::value_t::string).get<json::boolean_t>(), std::logic_error);
            CHECK_THROWS_AS(json(json::value_t::number_integer).get<json::boolean_t>(), std::logic_error);
            CHECK_THROWS_AS(json(json::value_t::number_unsigned).get<json::boolean_t>(), std::logic_error);
            CHECK_THROWS_AS(json(json::value_t::number_float).get<json::boolean_t>(), std::logic_error);

            CHECK_THROWS_WITH(json(json::value_t::null).get<json::boolean_t>(),
                              "type must be boolean, but is null");
            CHECK_THROWS_WITH(json(json::value_t::object).get<json::boolean_t>(),
                              "type must be boolean, but is object");
            CHECK_THROWS_WITH(json(json::value_t::array).get<json::boolean_t>(),
                              "type must be boolean, but is array");
            CHECK_THROWS_WITH(json(json::value_t::string).get<json::boolean_t>(),
                              "type must be boolean, but is string");
            CHECK_THROWS_WITH(json(json::value_t::number_integer).get<json::boolean_t>(),
                              "type must be boolean, but is number");
            CHECK_THROWS_WITH(json(json::value_t::number_unsigned).get<json::boolean_t>(),
                              "type must be boolean, but is number");
            CHECK_THROWS_WITH(json(json::value_t::number_float).get<json::boolean_t>(),
                              "type must be boolean, but is number");
        }
    }

    SECTION("get a boolean (implicit)")
    {
        json::boolean_t b_reference {true};
        json j(b_reference);

        SECTION("boolean_t")
        {
            json::boolean_t b = j;
            CHECK(json(b) == j);
        }

        SECTION("bool")
        {
            bool b = j;
            CHECK(json(b) == j);
        }
    }

    SECTION("get an integer number (explicit)")
    {
        json::number_integer_t n_reference {42};
        json j(n_reference);
        json::number_unsigned_t n_unsigned_reference {42u};
        json j_unsigned(n_unsigned_reference);

        SECTION("number_integer_t")
        {
            json::number_integer_t n = j.get<json::number_integer_t>();
            CHECK(json(n) == j);
        }

        SECTION("number_unsigned_t")
        {
            json::number_unsigned_t n = j_unsigned.get<json::number_unsigned_t>();
            CHECK(json(n) == j_unsigned);
        }

        SECTION("short")
        {
            short n = j.get<short>();
            CHECK(json(n) == j);
        }

        SECTION("unsigned short")
        {
            unsigned short n = j.get<unsigned short>();
            CHECK(json(n) == j);
        }

        SECTION("int")
        {
            int n = j.get<int>();
            CHECK(json(n) == j);
        }

        SECTION("unsigned int")
        {
            unsigned int n = j.get<unsigned int>();
            CHECK(json(n) == j);
        }

        SECTION("long")
        {
            long n = j.get<long>();
            CHECK(json(n) == j);
        }

        SECTION("unsigned long")
        {
            unsigned long n = j.get<unsigned long>();
            CHECK(json(n) == j);
        }

        SECTION("long long")
        {
            long long n = j.get<long long>();
            CHECK(json(n) == j);
        }

        SECTION("unsigned long long")
        {
            unsigned long long n = j.get<unsigned long long>();
            CHECK(json(n) == j);
        }

        SECTION("int8_t")
        {
            int8_t n = j.get<int8_t>();
            CHECK(json(n) == j);
        }

        SECTION("int16_t")
        {
            int16_t n = j.get<int16_t>();
            CHECK(json(n) == j);
        }

        SECTION("int32_t")
        {
            int32_t n = j.get<int32_t>();
            CHECK(json(n) == j);
        }

        SECTION("int64_t")
        {
            int64_t n = j.get<int64_t>();
            CHECK(json(n) == j);
        }

        SECTION("int8_fast_t")
        {
            int_fast8_t n = j.get<int_fast8_t>();
            CHECK(json(n) == j);
        }

        SECTION("int16_fast_t")
        {
            int_fast16_t n = j.get<int_fast16_t>();
            CHECK(json(n) == j);
        }

        SECTION("int32_fast_t")
        {
            int_fast32_t n = j.get<int_fast32_t>();
            CHECK(json(n) == j);
        }

        SECTION("int64_fast_t")
        {
            int_fast64_t n = j.get<int_fast64_t>();
            CHECK(json(n) == j);
        }

        SECTION("int8_least_t")
        {
            int_least8_t n = j.get<int_least8_t>();
            CHECK(json(n) == j);
        }

        SECTION("int16_least_t")
        {
            int_least16_t n = j.get<int_least16_t>();
            CHECK(json(n) == j);
        }

        SECTION("int32_least_t")
        {
            int_least32_t n = j.get<int_least32_t>();
            CHECK(json(n) == j);
        }

        SECTION("int64_least_t")
        {
            int_least64_t n = j.get<int_least64_t>();
            CHECK(json(n) == j);
        }

        SECTION("uint8_t")
        {
            uint8_t n = j.get<uint8_t>();
            CHECK(json(n) == j);
        }

        SECTION("uint16_t")
        {
            uint16_t n = j.get<uint16_t>();
            CHECK(json(n) == j);
        }

        SECTION("uint32_t")
        {
            uint32_t n = j.get<uint32_t>();
            CHECK(json(n) == j);
        }

        SECTION("uint64_t")
        {
            uint64_t n = j.get<uint64_t>();
            CHECK(json(n) == j);
        }

        SECTION("uint8_fast_t")
        {
            uint_fast8_t n = j.get<uint_fast8_t>();
            CHECK(json(n) == j);
        }

        SECTION("uint16_fast_t")
        {
            uint_fast16_t n = j.get<uint_fast16_t>();
            CHECK(json(n) == j);
        }

        SECTION("uint32_fast_t")
        {
            uint_fast32_t n = j.get<uint_fast32_t>();
            CHECK(json(n) == j);
        }

        SECTION("uint64_fast_t")
        {
            uint_fast64_t n = j.get<uint_fast64_t>();
            CHECK(json(n) == j);
        }

        SECTION("uint8_least_t")
        {
            uint_least8_t n = j.get<uint_least8_t>();
            CHECK(json(n) == j);
        }

        SECTION("uint16_least_t")
        {
            uint_least16_t n = j.get<uint_least16_t>();
            CHECK(json(n) == j);
        }

        SECTION("uint32_least_t")
        {
            uint_least32_t n = j.get<uint_least32_t>();
            CHECK(json(n) == j);
        }

        SECTION("uint64_least_t")
        {
            uint_least64_t n = j.get<uint_least64_t>();
            CHECK(json(n) == j);
        }

        SECTION("exception in case of a non-number type")
        {
            CHECK_THROWS_AS(json(json::value_t::null).get<json::number_integer_t>(), std::logic_error);
            CHECK_THROWS_AS(json(json::value_t::object).get<json::number_integer_t>(), std::logic_error);
            CHECK_THROWS_AS(json(json::value_t::array).get<json::number_integer_t>(), std::logic_error);
            CHECK_THROWS_AS(json(json::value_t::string).get<json::number_integer_t>(), std::logic_error);
            CHECK_THROWS_AS(json(json::value_t::boolean).get<json::number_integer_t>(), std::logic_error);

            CHECK_THROWS_WITH(json(json::value_t::null).get<json::number_integer_t>(),
                              "type must be number, but is null");
            CHECK_THROWS_WITH(json(json::value_t::object).get<json::number_integer_t>(),
                              "type must be number, but is object");
            CHECK_THROWS_WITH(json(json::value_t::array).get<json::number_integer_t>(),
                              "type must be number, but is array");
            CHECK_THROWS_WITH(json(json::value_t::string).get<json::number_integer_t>(),
                              "type must be number, but is string");
            CHECK_THROWS_WITH(json(json::value_t::boolean).get<json::number_integer_t>(),
                              "type must be number, but is boolean");

            CHECK_NOTHROW(json(json::value_t::number_float).get<json::number_integer_t>());
            CHECK_NOTHROW(json(json::value_t::number_float).get<json::number_unsigned_t>());
        }
    }

    SECTION("get an integer number (implicit)")
    {
        json::number_integer_t n_reference {42};
        json j(n_reference);
        json::number_unsigned_t n_unsigned_reference {42u};
        json j_unsigned(n_unsigned_reference);

        SECTION("number_integer_t")
        {
            json::number_integer_t n = j.get<json::number_integer_t>();
            CHECK(json(n) == j);
        }

        SECTION("number_unsigned_t")
        {
            json::number_unsigned_t n = j_unsigned.get<json::number_unsigned_t>();
            CHECK(json(n) == j_unsigned);
        }

        SECTION("short")
        {
            short n = j;
            CHECK(json(n) == j);
        }

        SECTION("unsigned short")
        {
            unsigned short n = j_unsigned;
            CHECK(json(n) == j_unsigned);
        }

        SECTION("int")
        {
            int n = j;
            CHECK(json(n) == j);
        }

        SECTION("unsigned int")
        {
            unsigned int n = j_unsigned;
            CHECK(json(n) == j_unsigned);
        }

        SECTION("long")
        {
            long n = j;
            CHECK(json(n) == j);
        }

        SECTION("unsigned long")
        {
            unsigned long n = j_unsigned;
            CHECK(json(n) == j_unsigned);
        }

        SECTION("long long")
        {
            long long n = j;
            CHECK(json(n) == j);
        }

        SECTION("unsigned long long")
        {
            unsigned long long n = j_unsigned;
            CHECK(json(n) == j_unsigned);
        }

        SECTION("int8_t")
        {
            int8_t n = j;
            CHECK(json(n) == j);
        }

        SECTION("int16_t")
        {
            int16_t n = j;
            CHECK(json(n) == j);
        }

        SECTION("int32_t")
        {
            int32_t n = j;
            CHECK(json(n) == j);
        }

        SECTION("int64_t")
        {
            int64_t n = j;
            CHECK(json(n) == j);
        }

        SECTION("int8_fast_t")
        {
            int_fast8_t n = j;
            CHECK(json(n) == j);
        }

        SECTION("int16_fast_t")
        {
            int_fast16_t n = j;
            CHECK(json(n) == j);
        }

        SECTION("int32_fast_t")
        {
            int_fast32_t n = j;
            CHECK(json(n) == j);
        }

        SECTION("int64_fast_t")
        {
            int_fast64_t n = j;
            CHECK(json(n) == j);
        }

        SECTION("int8_least_t")
        {
            int_least8_t n = j;
            CHECK(json(n) == j);
        }

        SECTION("int16_least_t")
        {
            int_least16_t n = j;
            CHECK(json(n) == j);
        }

        SECTION("int32_least_t")
        {
            int_least32_t n = j;
            CHECK(json(n) == j);
        }

        SECTION("int64_least_t")
        {
            int_least64_t n = j;
            CHECK(json(n) == j);
        }

        SECTION("uint8_t")
        {
            uint8_t n = j_unsigned;
            CHECK(json(n) == j_unsigned);
        }

        SECTION("uint16_t")
        {
            uint16_t n = j_unsigned;
            CHECK(json(n) == j_unsigned);
        }

        SECTION("uint32_t")
        {
            uint32_t n = j_unsigned;
            CHECK(json(n) == j_unsigned);
        }

        SECTION("uint64_t")
        {
            uint64_t n = j_unsigned;
            CHECK(json(n) == j_unsigned);
        }

        SECTION("uint8_fast_t")
        {
            uint_fast8_t n = j_unsigned;
            CHECK(json(n) == j_unsigned);
        }

        SECTION("uint16_fast_t")
        {
            uint_fast16_t n = j_unsigned;
            CHECK(json(n) == j_unsigned);
        }

        SECTION("uint32_fast_t")
        {
            uint_fast32_t n = j_unsigned;
            CHECK(json(n) == j_unsigned);
        }

        SECTION("uint64_fast_t")
        {
            uint_fast64_t n = j_unsigned;
            CHECK(json(n) == j_unsigned);
        }

        SECTION("uint8_least_t")
        {
            uint_least8_t n = j_unsigned;
            CHECK(json(n) == j_unsigned);
        }

        SECTION("uint16_least_t")
        {
            uint_least16_t n = j_unsigned;
            CHECK(json(n) == j_unsigned);
        }

        SECTION("uint32_least_t")
        {
            uint_least32_t n = j_unsigned;
            CHECK(json(n) == j_unsigned);
        }

        SECTION("uint64_least_t")
        {
            uint_least64_t n = j_unsigned;
            CHECK(json(n) == j_unsigned);
        }
    }

    SECTION("get a floating-point number (explicit)")
    {
        json::number_float_t n_reference {42.23};
        json j(n_reference);

        SECTION("number_float_t")
        {
            json::number_float_t n = j.get<json::number_float_t>();
            CHECK(json(n).m_value.number_float == Approx(j.m_value.number_float));
        }

        SECTION("float")
        {
            float n = j.get<float>();
            CHECK(json(n).m_value.number_float == Approx(j.m_value.number_float));
        }

        SECTION("double")
        {
            double n = j.get<double>();
            CHECK(json(n).m_value.number_float == Approx(j.m_value.number_float));
        }

        SECTION("exception in case of a non-string type")
        {
            CHECK_THROWS_AS(json(json::value_t::null).get<json::number_float_t>(), std::logic_error);
            CHECK_THROWS_AS(json(json::value_t::object).get<json::number_float_t>(), std::logic_error);
            CHECK_THROWS_AS(json(json::value_t::array).get<json::number_float_t>(), std::logic_error);
            CHECK_THROWS_AS(json(json::value_t::string).get<json::number_float_t>(), std::logic_error);
            CHECK_THROWS_AS(json(json::value_t::boolean).get<json::number_float_t>(), std::logic_error);

            CHECK_THROWS_WITH(json(json::value_t::null).get<json::number_float_t>(),
                              "type must be number, but is null");
            CHECK_THROWS_WITH(json(json::value_t::object).get<json::number_float_t>(),
                              "type must be number, but is object");
            CHECK_THROWS_WITH(json(json::value_t::array).get<json::number_float_t>(),
                              "type must be number, but is array");
            CHECK_THROWS_WITH(json(json::value_t::string).get<json::number_float_t>(),
                              "type must be number, but is string");
            CHECK_THROWS_WITH(json(json::value_t::boolean).get<json::number_float_t>(),
                              "type must be number, but is boolean");

            CHECK_NOTHROW(json(json::value_t::number_integer).get<json::number_float_t>());
            CHECK_NOTHROW(json(json::value_t::number_unsigned).get<json::number_float_t>());
        }
    }

    SECTION("get a floating-point number (implicit)")
    {
        json::number_float_t n_reference {42.23};
        json j(n_reference);

        SECTION("number_float_t")
        {
            json::number_float_t n = j;
            CHECK(json(n).m_value.number_float == Approx(j.m_value.number_float));
        }

        SECTION("float")
        {
            float n = j;
            CHECK(json(n).m_value.number_float == Approx(j.m_value.number_float));
        }

        SECTION("double")
        {
            double n = j;
            CHECK(json(n).m_value.number_float == Approx(j.m_value.number_float));
        }
    }

    SECTION("more involved conversions")
    {
        SECTION("object-like STL containers")
        {
            json j1 = {{"one", 1}, {"two", 2}, {"three", 3}};
            json j2 = {{"one", 1u}, {"two", 2u}, {"three", 3u}};
            json j3 = {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}};
            json j4 = {{"one", true}, {"two", false}, {"three", true}};
            json j5 = {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}};

            SECTION("std::map")
            {
                auto m1 = j1.get<std::map<std::string, int>>();
                auto m2 = j2.get<std::map<std::string, unsigned int>>();
                auto m3 = j3.get<std::map<std::string, double>>();
                auto m4 = j4.get<std::map<std::string, bool>>();
                //auto m5 = j5.get<std::map<std::string, std::string>>();
            }

            SECTION("std::unordered_map")
            {
                auto m1 = j1.get<std::unordered_map<std::string, int>>();
                auto m2 = j2.get<std::unordered_map<std::string, unsigned int>>();
                auto m3 = j3.get<std::unordered_map<std::string, double>>();
                auto m4 = j4.get<std::unordered_map<std::string, bool>>();
                //auto m5 = j5.get<std::unordered_map<std::string, std::string>>();
                //CHECK(m5["one"] == "eins");
            }

            SECTION("std::multimap")
            {
                auto m1 = j1.get<std::multimap<std::string, int>>();
                auto m2 = j2.get<std::multimap<std::string, unsigned int>>();
                auto m3 = j3.get<std::multimap<std::string, double>>();
                auto m4 = j4.get<std::multimap<std::string, bool>>();
                //auto m5 = j5.get<std::multimap<std::string, std::string>>();
                //CHECK(m5["one"] == "eins");
            }

            SECTION("std::unordered_multimap")
            {
                auto m1 = j1.get<std::unordered_multimap<std::string, int>>();
                auto m2 = j2.get<std::unordered_multimap<std::string, unsigned int>>();
                auto m3 = j3.get<std::unordered_multimap<std::string, double>>();
                auto m4 = j4.get<std::unordered_multimap<std::string, bool>>();
                //auto m5 = j5.get<std::unordered_multimap<std::string, std::string>>();
                //CHECK(m5["one"] == "eins");
            }

            SECTION("exception in case of a non-object type")
            {
                CHECK_THROWS_AS((json().get<std::map<std::string, int>>()), std::logic_error);
                CHECK_THROWS_WITH((json().get<std::map<std::string, int>>()), "type must be object, but is null");
            }
        }

        SECTION("array-like STL containers")
        {
            json j1 = {1, 2, 3, 4};
            json j2 = {1u, 2u, 3u, 4u};
            json j3 = {1.2, 2.3, 3.4, 4.5};
            json j4 = {true, false, true};
            json j5 = {"one", "two", "three"};

            SECTION("std::list")
            {
                auto m1 = j1.get<std::list<int>>();
                auto m2 = j2.get<std::list<unsigned int>>();
                auto m3 = j3.get<std::list<double>>();
                auto m4 = j4.get<std::list<bool>>();
                auto m5 = j5.get<std::list<std::string>>();
            }

            //SECTION("std::forward_list")
            //{
            //    auto m1 = j1.get<std::forward_list<int>>();
            //    auto m2 = j2.get<std::forward_list<unsigned int>>();
            //    auto m3 = j3.get<std::forward_list<double>>();
            //    auto m4 = j4.get<std::forward_list<bool>>();
            //    auto m5 = j5.get<std::forward_list<std::string>>();
            //}

            SECTION("std::vector")
            {
                auto m1 = j1.get<std::vector<int>>();
                auto m2 = j2.get<std::vector<unsigned int>>();
                auto m3 = j3.get<std::vector<double>>();
                auto m4 = j4.get<std::vector<bool>>();
                auto m5 = j5.get<std::vector<std::string>>();
            }

            SECTION("std::deque")
            {
                auto m1 = j1.get<std::deque<int>>();
                auto m2 = j2.get<std::deque<unsigned int>>();
                auto m3 = j2.get<std::deque<double>>();
                auto m4 = j4.get<std::deque<bool>>();
                auto m5 = j5.get<std::deque<std::string>>();
            }

            SECTION("std::set")
            {
                auto m1 = j1.get<std::set<int>>();
                auto m2 = j2.get<std::set<unsigned int>>();
                auto m3 = j3.get<std::set<double>>();
                auto m4 = j4.get<std::set<bool>>();
                auto m5 = j5.get<std::set<std::string>>();
            }

            SECTION("std::unordered_set")
            {
                auto m1 = j1.get<std::unordered_set<int>>();
                auto m2 = j2.get<std::unordered_set<unsigned int>>();
                auto m3 = j3.get<std::unordered_set<double>>();
                auto m4 = j4.get<std::unordered_set<bool>>();
                auto m5 = j5.get<std::unordered_set<std::string>>();
            }

            SECTION("exception in case of a non-object type")
            {
                CHECK_THROWS_AS((json().get<std::list<int>>()), std::logic_error);
                CHECK_THROWS_AS((json().get<std::vector<int>>()), std::logic_error);
                CHECK_THROWS_AS((json().get<std::vector<json>>()), std::logic_error);
                CHECK_THROWS_AS((json().get<std::list<json>>()), std::logic_error);

                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");
                CHECK_THROWS_WITH((json().get<std::list<json>>()), "type must be array, but is null");
            }
        }
    }
}

TEST_CASE("pointer access")
{
    // create a JSON value with different types
    json json_types =
    {
        {"boolean", true},
        {
            "number", {
                {"integer", 42},
                {"unsigned", 42u},
                {"floating-point", 17.23}
            }
        },
        {"string", "Hello, world!"},
        {"array", {1, 2, 3, 4, 5}},
        {"null", nullptr}
    };

    SECTION("pointer access to object_t")
    {
        using test_type = json::object_t;
        json value = {{"one", 1}, {"two", 2}};

        // check if pointers are returned correctly
        test_type* p1 = value.get_ptr<test_type*>();
        CHECK(p1 == value.get_ptr<test_type*>());
        CHECK(*p1 == value.get<test_type>());

        const test_type* p2 = value.get_ptr<const test_type*>();
        CHECK(p1 == value.get_ptr<const test_type*>());
        CHECK(*p2 == value.get<test_type>());

        const test_type* const p3 = value.get_ptr<const test_type* const>();
        CHECK(p1 == value.get_ptr<const test_type* const>());
        CHECK(*p3 == value.get<test_type>());

        // check if null pointers are returned correctly
        CHECK(value.get_ptr<json::object_t*>() != nullptr);
        CHECK(value.get_ptr<json::array_t*>() == nullptr);
        CHECK(value.get_ptr<json::string_t*>() == nullptr);
        CHECK(value.get_ptr<json::boolean_t*>() == nullptr);
        CHECK(value.get_ptr<json::number_integer_t*>() == nullptr);
        CHECK(value.get_ptr<json::number_unsigned_t*>() == nullptr);
        CHECK(value.get_ptr<json::number_float_t*>() == nullptr);
    }

    SECTION("pointer access to const object_t")
    {
        using test_type = json::object_t;
        const json value = {{"one", 1}, {"two", 2}};

        // this should not compile
        // test_type* p1 = value.get_ptr<test_type*>();

        // check if pointers are returned correctly
        const test_type* p2 = value.get_ptr<const test_type*>();
        CHECK(*p2 == value.get<test_type>());

        const test_type* const p3 = value.get_ptr<const test_type* const>();
        CHECK(p2 == p3);
    }

    SECTION("pointer access to array_t")
    {
        using test_type = json::array_t;
        json value = {1, 2, 3, 4};

        // check if pointers are returned correctly
        test_type* p1 = value.get_ptr<test_type*>();
        CHECK(p1 == value.get_ptr<test_type*>());
        CHECK(*p1 == value.get<test_type>());

        const test_type* p2 = value.get_ptr<const test_type*>();
        CHECK(p1 == value.get_ptr<const test_type*>());
        CHECK(*p2 == value.get<test_type>());

        const test_type* const p3 = value.get_ptr<const test_type* const>();
        CHECK(p1 == value.get_ptr<const test_type* const>());
        CHECK(*p3 == value.get<test_type>());

        // check if null pointers are returned correctly
        CHECK(value.get_ptr<json::object_t*>() == nullptr);
        CHECK(value.get_ptr<json::array_t*>() != nullptr);
        CHECK(value.get_ptr<json::string_t*>() == nullptr);
        CHECK(value.get_ptr<json::boolean_t*>() == nullptr);
        CHECK(value.get_ptr<json::number_integer_t*>() == nullptr);
        CHECK(value.get_ptr<json::number_unsigned_t*>() == nullptr);
        CHECK(value.get_ptr<json::number_float_t*>() == nullptr);
    }

    SECTION("pointer access to string_t")
    {
        using test_type = json::string_t;
        json value = "hello";

        // check if pointers are returned correctly
        test_type* p1 = value.get_ptr<test_type*>();
        CHECK(p1 == value.get_ptr<test_type*>());
        CHECK(*p1 == value.get<test_type>());

        const test_type* p2 = value.get_ptr<const test_type*>();
        CHECK(p1 == value.get_ptr<const test_type*>());
        CHECK(*p2 == value.get<test_type>());

        const test_type* const p3 = value.get_ptr<const test_type* const>();
        CHECK(p1 == value.get_ptr<const test_type* const>());
        CHECK(*p3 == value.get<test_type>());

        // check if null pointers are returned correctly
        CHECK(value.get_ptr<json::object_t*>() == nullptr);
        CHECK(value.get_ptr<json::array_t*>() == nullptr);
        CHECK(value.get_ptr<json::string_t*>() != nullptr);
        CHECK(value.get_ptr<json::boolean_t*>() == nullptr);
        CHECK(value.get_ptr<json::number_integer_t*>() == nullptr);
        CHECK(value.get_ptr<json::number_unsigned_t*>() == nullptr);
        CHECK(value.get_ptr<json::number_float_t*>() == nullptr);
    }

    SECTION("pointer access to boolean_t")
    {
        using test_type = json::boolean_t;
        json value = false;

        // check if pointers are returned correctly
        test_type* p1 = value.get_ptr<test_type*>();
        CHECK(p1 == value.get_ptr<test_type*>());
        CHECK(*p1 == value.get<test_type>());

        const test_type* p2 = value.get_ptr<const test_type*>();
        CHECK(p1 == value.get_ptr<const test_type*>());
        CHECK(*p2 == value.get<test_type>());

        const test_type* const p3 = value.get_ptr<const test_type* const>();
        CHECK(p1 == value.get_ptr<const test_type* const>());
        CHECK(*p3 == value.get<test_type>());

        // check if null pointers are returned correctly
        CHECK(value.get_ptr<json::object_t*>() == nullptr);
        CHECK(value.get_ptr<json::array_t*>() == nullptr);
        CHECK(value.get_ptr<json::string_t*>() == nullptr);
        CHECK(value.get_ptr<json::boolean_t*>() != nullptr);
        CHECK(value.get_ptr<json::number_integer_t*>() == nullptr);
        CHECK(value.get_ptr<json::number_unsigned_t*>() == nullptr);
        CHECK(value.get_ptr<json::number_float_t*>() == nullptr);
    }

    SECTION("pointer access to number_integer_t")
    {
        using test_type = json::number_integer_t;
        json value = 23;

        // check if pointers are returned correctly
        test_type* p1 = value.get_ptr<test_type*>();
        CHECK(p1 == value.get_ptr<test_type*>());
        CHECK(*p1 == value.get<test_type>());

        const test_type* p2 = value.get_ptr<const test_type*>();
        CHECK(p1 == value.get_ptr<const test_type*>());
        CHECK(*p2 == value.get<test_type>());

        const test_type* const p3 = value.get_ptr<const test_type* const>();
        CHECK(p1 == value.get_ptr<const test_type* const>());
        CHECK(*p3 == value.get<test_type>());

        // check if null pointers are returned correctly
        CHECK(value.get_ptr<json::object_t*>() == nullptr);
        CHECK(value.get_ptr<json::array_t*>() == nullptr);
        CHECK(value.get_ptr<json::string_t*>() == nullptr);
        CHECK(value.get_ptr<json::boolean_t*>() == nullptr);
        CHECK(value.get_ptr<json::number_integer_t*>() != nullptr);
        CHECK(value.get_ptr<json::number_unsigned_t*>() == nullptr);
        CHECK(value.get_ptr<json::number_float_t*>() == nullptr);
    }

    SECTION("pointer access to number_unsigned_t")
    {
        using test_type = json::number_unsigned_t;
        json value = 23u;

        // check if pointers are returned correctly
        test_type* p1 = value.get_ptr<test_type*>();
        CHECK(p1 == value.get_ptr<test_type*>());
        CHECK(*p1 == value.get<test_type>());

        const test_type* p2 = value.get_ptr<const test_type*>();
        CHECK(p1 == value.get_ptr<const test_type*>());
        CHECK(*p2 == value.get<test_type>());

        const test_type* const p3 = value.get_ptr<const test_type* const>();
        CHECK(p1 == value.get_ptr<const test_type* const>());
        CHECK(*p3 == value.get<test_type>());

        // check if null pointers are returned correctly
        CHECK(value.get_ptr<json::object_t*>() == nullptr);
        CHECK(value.get_ptr<json::array_t*>() == nullptr);
        CHECK(value.get_ptr<json::string_t*>() == nullptr);
        CHECK(value.get_ptr<json::boolean_t*>() == nullptr);
        CHECK(value.get_ptr<json::number_integer_t*>() != nullptr);
        CHECK(value.get_ptr<json::number_unsigned_t*>() != nullptr);
        CHECK(value.get_ptr<json::number_float_t*>() == nullptr);
    }

    SECTION("pointer access to number_float_t")
    {
        using test_type = json::number_float_t;
        json value = 42.23;

        // check if pointers are returned correctly
        test_type* p1 = value.get_ptr<test_type*>();
        CHECK(p1 == value.get_ptr<test_type*>());
        CHECK(*p1 == Approx(value.get<test_type>()));

        const test_type* p2 = value.get_ptr<const test_type*>();
        CHECK(p1 == value.get_ptr<const test_type*>());
        CHECK(*p2 == Approx(value.get<test_type>()));

        const test_type* const p3 = value.get_ptr<const test_type* const>();
        CHECK(p1 == value.get_ptr<const test_type* const>());
        CHECK(*p3 == Approx(value.get<test_type>()));

        // check if null pointers are returned correctly
        CHECK(value.get_ptr<json::object_t*>() == nullptr);
        CHECK(value.get_ptr<json::array_t*>() == nullptr);
        CHECK(value.get_ptr<json::string_t*>() == nullptr);
        CHECK(value.get_ptr<json::boolean_t*>() == nullptr);
        CHECK(value.get_ptr<json::number_integer_t*>() == nullptr);
        CHECK(value.get_ptr<json::number_unsigned_t*>() == nullptr);
        CHECK(value.get_ptr<json::number_float_t*>() != nullptr);
    }
}

TEST_CASE("reference access")
{
    // create a JSON value with different types
    json json_types =
    {
        {"boolean", true},
        {
            "number", {
                {"integer", 42},
                {"floating-point", 17.23}
            }
        },
        {"string", "Hello, world!"},
        {"array", {1, 2, 3, 4, 5}},
        {"null", nullptr}
    };

    SECTION("reference access to object_t")
    {
        using test_type = json::object_t;
        json value = {{"one", 1}, {"two", 2}};

        // check if references are returned correctly
        test_type& p1 = value.get_ref<test_type&>();
        CHECK(&p1 == value.get_ptr<test_type*>());
        CHECK(p1 == value.get<test_type>());

        const test_type& p2 = value.get_ref<const test_type&>();
        CHECK(&p2 == value.get_ptr<const test_type*>());
        CHECK(p2 == value.get<test_type>());

        // check if mismatching references throw correctly
        CHECK_NOTHROW(value.get_ref<json::object_t&>());
        CHECK_THROWS(value.get_ref<json::array_t&>());
        CHECK_THROWS(value.get_ref<json::string_t&>());
        CHECK_THROWS(value.get_ref<json::boolean_t&>());
        CHECK_THROWS(value.get_ref<json::number_integer_t&>());
        CHECK_THROWS(value.get_ref<json::number_float_t&>());
    }

    SECTION("const reference access to const object_t")
    {
        using test_type = json::object_t;
        const json value = {{"one", 1}, {"two", 2}};

        // this should not compile
        // test_type& p1 = value.get_ref<test_type&>();

        // check if references are returned correctly
        const test_type& p2 = value.get_ref<const test_type&>();
        CHECK(&p2 == value.get_ptr<const test_type*>());
        CHECK(p2 == value.get<test_type>());
    }

    SECTION("reference access to array_t")
    {
        using test_type = json::array_t;
        json value = {1, 2, 3, 4};

        // check if references are returned correctly
        test_type& p1 = value.get_ref<test_type&>();
        CHECK(&p1 == value.get_ptr<test_type*>());
        CHECK(p1 == value.get<test_type>());

        const test_type& p2 = value.get_ref<const test_type&>();
        CHECK(&p2 == value.get_ptr<const test_type*>());
        CHECK(p2 == value.get<test_type>());

        // check if mismatching references throw correctly
        CHECK_THROWS(value.get_ref<json::object_t&>());
        CHECK_NOTHROW(value.get_ref<json::array_t&>());
        CHECK_THROWS(value.get_ref<json::string_t&>());
        CHECK_THROWS(value.get_ref<json::boolean_t&>());
        CHECK_THROWS(value.get_ref<json::number_integer_t&>());
        CHECK_THROWS(value.get_ref<json::number_float_t&>());
    }

    SECTION("reference access to string_t")
    {
        using test_type = json::string_t;
        json value = "hello";

        // check if references are returned correctly
        test_type& p1 = value.get_ref<test_type&>();
        CHECK(&p1 == value.get_ptr<test_type*>());
        CHECK(p1 == value.get<test_type>());

        const test_type& p2 = value.get_ref<const test_type&>();
        CHECK(&p2 == value.get_ptr<const test_type*>());
        CHECK(p2 == value.get<test_type>());

        // check if mismatching references throw correctly
        CHECK_THROWS(value.get_ref<json::object_t&>());
        CHECK_THROWS(value.get_ref<json::array_t&>());
        CHECK_NOTHROW(value.get_ref<json::string_t&>());
        CHECK_THROWS(value.get_ref<json::boolean_t&>());
        CHECK_THROWS(value.get_ref<json::number_integer_t&>());
        CHECK_THROWS(value.get_ref<json::number_float_t&>());
    }

    SECTION("reference access to boolean_t")
    {
        using test_type = json::boolean_t;
        json value = false;

        // check if references are returned correctly
        test_type& p1 = value.get_ref<test_type&>();
        CHECK(&p1 == value.get_ptr<test_type*>());
        CHECK(p1 == value.get<test_type>());

        const test_type& p2 = value.get_ref<const test_type&>();
        CHECK(&p2 == value.get_ptr<const test_type*>());
        CHECK(p2 == value.get<test_type>());

        // check if mismatching references throw correctly
        CHECK_THROWS(value.get_ref<json::object_t&>());
        CHECK_THROWS(value.get_ref<json::array_t&>());
        CHECK_THROWS(value.get_ref<json::string_t&>());
        CHECK_NOTHROW(value.get_ref<json::boolean_t&>());
        CHECK_THROWS(value.get_ref<json::number_integer_t&>());
        CHECK_THROWS(value.get_ref<json::number_float_t&>());
    }

    SECTION("reference access to number_integer_t")
    {
        using test_type = json::number_integer_t;
        json value = 23;

        // check if references are returned correctly
        test_type& p1 = value.get_ref<test_type&>();
        CHECK(&p1 == value.get_ptr<test_type*>());
        CHECK(p1 == value.get<test_type>());

        const test_type& p2 = value.get_ref<const test_type&>();
        CHECK(&p2 == value.get_ptr<const test_type*>());
        CHECK(p2 == value.get<test_type>());

        // check if mismatching references throw correctly
        CHECK_THROWS(value.get_ref<json::object_t&>());
        CHECK_THROWS(value.get_ref<json::array_t&>());
        CHECK_THROWS(value.get_ref<json::string_t&>());
        CHECK_THROWS(value.get_ref<json::boolean_t&>());
        CHECK_NOTHROW(value.get_ref<json::number_integer_t&>());
        CHECK_THROWS(value.get_ref<json::number_float_t&>());
    }

    SECTION("reference access to number_float_t")
    {
        using test_type = json::number_float_t;
        json value = 42.23;

        // check if references are returned correctly
        test_type& p1 = value.get_ref<test_type&>();
        CHECK(&p1 == value.get_ptr<test_type*>());
        CHECK(p1 == value.get<test_type>());

        const test_type& p2 = value.get_ref<const test_type&>();
        CHECK(&p2 == value.get_ptr<const test_type*>());
        CHECK(p2 == value.get<test_type>());

        // check if mismatching references throw correctly
        CHECK_THROWS(value.get_ref<json::object_t&>());
        CHECK_THROWS(value.get_ref<json::array_t&>());
        CHECK_THROWS(value.get_ref<json::string_t&>());
        CHECK_THROWS(value.get_ref<json::boolean_t&>());
        CHECK_THROWS(value.get_ref<json::number_integer_t&>());
        CHECK_NOTHROW(value.get_ref<json::number_float_t&>());
    }
}

TEST_CASE("element access")
{
    SECTION("array")
    {
        json j = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
        const json j_const = j;

        SECTION("access specified element with bounds checking")
        {
            SECTION("access within bounds")
            {
                CHECK(j.at(0) == json(1));
                CHECK(j.at(1) == json(1u));
                CHECK(j.at(2) == json(true));
                CHECK(j.at(3) == json(nullptr));
                CHECK(j.at(4) == json("string"));
                CHECK(j.at(5) == json(42.23));
                CHECK(j.at(6) == json(json::object()));
                CHECK(j.at(7) == json({1, 2, 3}));

                CHECK(j_const.at(0) == json(1));
                CHECK(j_const.at(1) == json(1u));
                CHECK(j_const.at(2) == json(true));
                CHECK(j_const.at(3) == json(nullptr));
                CHECK(j_const.at(4) == json("string"));
                CHECK(j_const.at(5) == json(42.23));
                CHECK(j_const.at(6) == json(json::object()));
                CHECK(j_const.at(7) == json({1, 2, 3}));
            }

            SECTION("access outside bounds")
            {
                CHECK_THROWS_AS(j.at(8), std::out_of_range);
                CHECK_THROWS_AS(j_const.at(8), std::out_of_range);

                CHECK_THROWS_WITH(j.at(8), "array index 8 is out of range");
                CHECK_THROWS_WITH(j_const.at(8), "array index 8 is out of range");
            }

            SECTION("access on non-array type")
            {
                SECTION("null")
                {
                    json j_nonarray(json::value_t::null);
                    const json j_nonarray_const(j_nonarray);
                    CHECK_THROWS_AS(j_nonarray.at(0), std::domain_error);
                    CHECK_THROWS_AS(j_nonarray_const.at(0), std::domain_error);

                    CHECK_THROWS_WITH(j_nonarray.at(0), "cannot use at() with null");
                    CHECK_THROWS_WITH(j_nonarray_const.at(0), "cannot use at() with null");
                }

                SECTION("boolean")
                {
                    json j_nonarray(json::value_t::boolean);
                    const json j_nonarray_const(j_nonarray);
                    CHECK_THROWS_AS(j_nonarray.at(0), std::domain_error);
                    CHECK_THROWS_AS(j_nonarray_const.at(0), std::domain_error);

                    CHECK_THROWS_WITH(j_nonarray.at(0), "cannot use at() with boolean");
                    CHECK_THROWS_WITH(j_nonarray_const.at(0), "cannot use at() with boolean");
                }

                SECTION("string")
                {
                    json j_nonarray(json::value_t::string);
                    const json j_nonarray_const(j_nonarray);
                    CHECK_THROWS_AS(j_nonarray.at(0), std::domain_error);
                    CHECK_THROWS_AS(j_nonarray_const.at(0), std::domain_error);

                    CHECK_THROWS_WITH(j_nonarray.at(0), "cannot use at() with string");
                    CHECK_THROWS_WITH(j_nonarray_const.at(0), "cannot use at() with string");
                }

                SECTION("object")
                {
                    json j_nonarray(json::value_t::object);
                    const json j_nonarray_const(j_nonarray);
                    CHECK_THROWS_AS(j_nonarray.at(0), std::domain_error);
                    CHECK_THROWS_AS(j_nonarray_const.at(0), std::domain_error);

                    CHECK_THROWS_WITH(j_nonarray.at(0), "cannot use at() with object");
                    CHECK_THROWS_WITH(j_nonarray_const.at(0), "cannot use at() with object");
                }

                SECTION("number (integer)")
                {
                    json j_nonarray(json::value_t::number_integer);
                    const json j_nonarray_const(j_nonarray);
                    CHECK_THROWS_AS(j_nonarray.at(0), std::domain_error);
                    CHECK_THROWS_AS(j_nonarray_const.at(0), std::domain_error);

                    CHECK_THROWS_WITH(j_nonarray.at(0), "cannot use at() with number");
                    CHECK_THROWS_WITH(j_nonarray_const.at(0), "cannot use at() with number");
                }

                SECTION("number (unsigned)")
                {
                    json j_nonarray(json::value_t::number_unsigned);
                    const json j_nonarray_const(j_nonarray);
                    CHECK_THROWS_AS(j_nonarray.at(0), std::domain_error);
                    CHECK_THROWS_AS(j_nonarray_const.at(0), std::domain_error);

                    CHECK_THROWS_WITH(j_nonarray.at(0), "cannot use at() with number");
                    CHECK_THROWS_WITH(j_nonarray_const.at(0), "cannot use at() with number");
                }

                SECTION("number (floating-point)")
                {
                    json j_nonarray(json::value_t::number_float);
                    const json j_nonarray_const(j_nonarray);
                    CHECK_THROWS_AS(j_nonarray.at(0), std::domain_error);
                    CHECK_THROWS_AS(j_nonarray_const.at(0), std::domain_error);

                    CHECK_THROWS_WITH(j_nonarray.at(0), "cannot use at() with number");
                    CHECK_THROWS_WITH(j_nonarray_const.at(0), "cannot use at() with number");
                }
            }
        }

        SECTION("front and back")
        {
            CHECK(j.front() == json(1));
            CHECK(j_const.front() == json(1));
            CHECK(j.back() == json({1, 2, 3}));
            CHECK(j_const.back() == json({1, 2, 3}));
        }

        SECTION("access specified element")
        {
            SECTION("access within bounds")
            {
                CHECK(j[0] == json(1));
                CHECK(j[1] == json(1u));
                CHECK(j[2] == json(true));
                CHECK(j[3] == json(nullptr));
                CHECK(j[4] == json("string"));
                CHECK(j[5] == json(42.23));
                CHECK(j[6] == json(json::object()));
                CHECK(j[7] == json({1, 2, 3}));

                CHECK(j_const[0] == json(1));
                CHECK(j_const[1] == json(1u));
                CHECK(j_const[2] == json(true));
                CHECK(j_const[3] == json(nullptr));
                CHECK(j_const[4] == json("string"));
                CHECK(j_const[5] == json(42.23));
                CHECK(j_const[6] == json(json::object()));
                CHECK(j_const[7] == json({1, 2, 3}));
            }

            SECTION("access on non-array type")
            {
                SECTION("null")
                {
                    SECTION("standard tests")
                    {
                        json j_nonarray(json::value_t::null);
                        const json j_nonarray_const(j_nonarray);
                        CHECK_NOTHROW(j_nonarray[0]);
                        CHECK_THROWS_AS(j_nonarray_const[0], std::domain_error);
                        CHECK_THROWS_WITH(j_nonarray_const[0], "cannot use operator[] with null");
                    }

                    SECTION("implicit transformation to properly filled array")
                    {
                        json j_nonarray;
                        j_nonarray[3] = 42;
                        CHECK(j_nonarray == json({nullptr, nullptr, nullptr, 42}));
                    }
                }

                SECTION("boolean")
                {
                    json j_nonarray(json::value_t::boolean);
                    const json j_nonarray_const(j_nonarray);
                    CHECK_THROWS_AS(j_nonarray[0], std::domain_error);
                    CHECK_THROWS_AS(j_nonarray_const[0], std::domain_error);
                    CHECK_THROWS_WITH(j_nonarray[0], "cannot use operator[] with boolean");
                    CHECK_THROWS_WITH(j_nonarray_const[0], "cannot use operator[] with boolean");
                }

                SECTION("string")
                {
                    json j_nonarray(json::value_t::string);
                    const json j_nonarray_const(j_nonarray);
                    CHECK_THROWS_AS(j_nonarray[0], std::domain_error);
                    CHECK_THROWS_AS(j_nonarray_const[0], std::domain_error);
                    CHECK_THROWS_WITH(j_nonarray[0], "cannot use operator[] with string");
                    CHECK_THROWS_WITH(j_nonarray_const[0], "cannot use operator[] with string");
                }

                SECTION("object")
                {
                    json j_nonarray(json::value_t::object);
                    const json j_nonarray_const(j_nonarray);
                    CHECK_THROWS_AS(j_nonarray[0], std::domain_error);
                    CHECK_THROWS_AS(j_nonarray_const[0], std::domain_error);
                    CHECK_THROWS_WITH(j_nonarray[0], "cannot use operator[] with object");
                    CHECK_THROWS_WITH(j_nonarray_const[0], "cannot use operator[] with object");
                }

                SECTION("number (integer)")
                {
                    json j_nonarray(json::value_t::number_integer);
                    const json j_nonarray_const(j_nonarray);
                    CHECK_THROWS_AS(j_nonarray[0], std::domain_error);
                    CHECK_THROWS_AS(j_nonarray_const[0], std::domain_error);
                    CHECK_THROWS_WITH(j_nonarray[0], "cannot use operator[] with number");
                    CHECK_THROWS_WITH(j_nonarray_const[0], "cannot use operator[] with number");
                }

                SECTION("number (unsigned)")
                {
                    json j_nonarray(json::value_t::number_unsigned);
                    const json j_nonarray_const(j_nonarray);
                    CHECK_THROWS_AS(j_nonarray[0], std::domain_error);
                    CHECK_THROWS_AS(j_nonarray_const[0], std::domain_error);
                    CHECK_THROWS_WITH(j_nonarray[0], "cannot use operator[] with number");
                    CHECK_THROWS_WITH(j_nonarray_const[0], "cannot use operator[] with number");
                }

                SECTION("number (floating-point)")
                {
                    json j_nonarray(json::value_t::number_float);
                    const json j_nonarray_const(j_nonarray);
                    CHECK_THROWS_AS(j_nonarray[0], std::domain_error);
                    CHECK_THROWS_AS(j_nonarray_const[0], std::domain_error);
                    CHECK_THROWS_WITH(j_nonarray[0], "cannot use operator[] with number");
                    CHECK_THROWS_WITH(j_nonarray_const[0], "cannot use operator[] with number");
                }
            }
        }

        SECTION("remove specified element")
        {
            SECTION("remove element by index")
            {
                {
                    json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
                    jarray.erase(0);
                    CHECK(jarray == json({1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}));
                }
                {
                    json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
                    jarray.erase(1);
                    CHECK(jarray == json({1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}));
                }
                {
                    json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
                    jarray.erase(2);
                    CHECK(jarray == json({1, 1u, nullptr, "string", 42.23, json::object(), {1, 2, 3}}));
                }
                {
                    json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
                    jarray.erase(3);
                    CHECK(jarray == json({1, 1u, true, "string", 42.23, json::object(), {1, 2, 3}}));
                }
                {
                    json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
                    jarray.erase(4);
                    CHECK(jarray == json({1, 1u, true, nullptr, 42.23, json::object(), {1, 2, 3}}));
                }
                {
                    json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
                    jarray.erase(5);
                    CHECK(jarray == json({1, 1u, true, nullptr, "string", json::object(), {1, 2, 3}}));
                }
                {
                    json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
                    jarray.erase(6);
                    CHECK(jarray == json({1, 1u, true, nullptr, "string", 42.23, {1, 2, 3}}));
                }
                {
                    json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
                    jarray.erase(7);
                    CHECK(jarray == json({1, 1u, true, nullptr, "string", 42.23, json::object()}));
                }
                {
                    json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
                    CHECK_THROWS_AS(jarray.erase(8), std::out_of_range);
                    CHECK_THROWS_WITH(jarray.erase(8), "array index 8 is out of range");
                }
            }

            SECTION("remove element by iterator")
            {
                SECTION("erase(begin())")
                {
                    {
                        json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
                        json::iterator it2 = jarray.erase(jarray.begin());
                        CHECK(jarray == json({1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}));
                        CHECK(*it2 == json(1u));
                    }
                    {
                        json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
                        json::const_iterator it2 = jarray.erase(jarray.cbegin());
                        CHECK(jarray == json({1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}));
                        CHECK(*it2 == json(1u));
                    }
                }

                SECTION("erase(begin(), end())")
                {
                    {
                        json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
                        json::iterator it2 = jarray.erase(jarray.begin(), jarray.end());
                        CHECK(jarray == json::array());
                        CHECK(it2 == jarray.end());
                    }
                    {
                        json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
                        json::const_iterator it2 = jarray.erase(jarray.cbegin(), jarray.cend());
                        CHECK(jarray == json::array());
                        CHECK(it2 == jarray.cend());
                    }
                }

                SECTION("erase(begin(), begin())")
                {
                    {
                        json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
                        json::iterator it2 = jarray.erase(jarray.begin(), jarray.begin());
                        CHECK(jarray == json({1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}));
                        CHECK(*it2 == json(1));
                    }
                    {
                        json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
                        json::const_iterator it2 = jarray.erase(jarray.cbegin(), jarray.cbegin());
                        CHECK(jarray == json({1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}));
                        CHECK(*it2 == json(1));
                    }
                }

                SECTION("erase at offset")
                {
                    {
                        json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
                        json::iterator it = jarray.begin() + 4;
                        json::iterator it2 = jarray.erase(it);
                        CHECK(jarray == json({1, 1u, true, nullptr, 42.23, json::object(), {1, 2, 3}}));
                        CHECK(*it2 == json(42.23));
                    }
                    {
                        json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
                        json::const_iterator it = jarray.cbegin() + 4;
                        json::const_iterator it2 = jarray.erase(it);
                        CHECK(jarray == json({1, 1u, true, nullptr, 42.23, json::object(), {1, 2, 3}}));
                        CHECK(*it2 == json(42.23));
                    }
                }

                SECTION("erase subrange")
                {
                    {
                        json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
                        json::iterator it2 = jarray.erase(jarray.begin() + 3, jarray.begin() + 6);
                        CHECK(jarray == json({1, 1u, true, json::object(), {1, 2, 3}}));
                        CHECK(*it2 == json::object());
                    }
                    {
                        json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
                        json::const_iterator it2 = jarray.erase(jarray.cbegin() + 3, jarray.cbegin() + 6);
                        CHECK(jarray == json({1, 1u, true, json::object(), {1, 2, 3}}));
                        CHECK(*it2 == json::object());
                    }
                }

                SECTION("different arrays")
                {
                    {
                        json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
                        json jarray2 = {"foo", "bar"};
                        CHECK_THROWS_AS(jarray.erase(jarray2.begin()), std::domain_error);
                        CHECK_THROWS_AS(jarray.erase(jarray.begin(), jarray2.end()), std::domain_error);
                        CHECK_THROWS_AS(jarray.erase(jarray2.begin(), jarray.end()), std::domain_error);
                        CHECK_THROWS_AS(jarray.erase(jarray2.begin(), jarray2.end()), std::domain_error);

                        CHECK_THROWS_WITH(jarray.erase(jarray2.begin()), "iterator does not fit current value");
                        CHECK_THROWS_WITH(jarray.erase(jarray.begin(), jarray2.end()),
                                          "iterators do not fit current value");
                        CHECK_THROWS_WITH(jarray.erase(jarray2.begin(), jarray.end()),
                                          "iterators do not fit current value");
                        CHECK_THROWS_WITH(jarray.erase(jarray2.begin(), jarray2.end()),
                                          "iterators do not fit current value");
                    }
                    {
                        json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
                        json jarray2 = {"foo", "bar"};
                        CHECK_THROWS_AS(jarray.erase(jarray2.cbegin()), std::domain_error);
                        CHECK_THROWS_AS(jarray.erase(jarray.cbegin(), jarray2.cend()), std::domain_error);
                        CHECK_THROWS_AS(jarray.erase(jarray2.cbegin(), jarray.cend()), std::domain_error);
                        CHECK_THROWS_AS(jarray.erase(jarray2.cbegin(), jarray2.cend()), std::domain_error);

                        CHECK_THROWS_WITH(jarray.erase(jarray2.cbegin()), "iterator does not fit current value");
                        CHECK_THROWS_WITH(jarray.erase(jarray.cbegin(), jarray2.cend()),
                                          "iterators do not fit current value");
                        CHECK_THROWS_WITH(jarray.erase(jarray2.cbegin(), jarray.cend()),
                                          "iterators do not fit current value");
                        CHECK_THROWS_WITH(jarray.erase(jarray2.cbegin(), jarray2.cend()),
                                          "iterators do not fit current value");
                    }
                }
            }

            SECTION("remove element by index in non-array type")
            {
                SECTION("null")
                {
                    json j_nonobject(json::value_t::null);
                    CHECK_THROWS_AS(j_nonobject.erase(0), std::domain_error);
                    CHECK_THROWS_WITH(j_nonobject.erase(0), "cannot use erase() with null");
                }

                SECTION("boolean")
                {
                    json j_nonobject(json::value_t::boolean);
                    CHECK_THROWS_AS(j_nonobject.erase(0), std::domain_error);
                    CHECK_THROWS_WITH(j_nonobject.erase(0), "cannot use erase() with boolean");
                }

                SECTION("string")
                {
                    json j_nonobject(json::value_t::string);
                    CHECK_THROWS_AS(j_nonobject.erase(0), std::domain_error);
                    CHECK_THROWS_WITH(j_nonobject.erase(0), "cannot use erase() with string");
                }

                SECTION("object")
                {
                    json j_nonobject(json::value_t::object);
                    CHECK_THROWS_AS(j_nonobject.erase(0), std::domain_error);
                    CHECK_THROWS_WITH(j_nonobject.erase(0), "cannot use erase() with object");
                }

                SECTION("number (integer)")
                {
                    json j_nonobject(json::value_t::number_integer);
                    CHECK_THROWS_AS(j_nonobject.erase(0), std::domain_error);
                    CHECK_THROWS_WITH(j_nonobject.erase(0), "cannot use erase() with number");
                }

                SECTION("number (unsigned)")
                {
                    json j_nonobject(json::value_t::number_unsigned);
                    CHECK_THROWS_AS(j_nonobject.erase(0), std::domain_error);
                    CHECK_THROWS_WITH(j_nonobject.erase(0), "cannot use erase() with number");
                }

                SECTION("number (floating-point)")
                {
                    json j_nonobject(json::value_t::number_float);
                    CHECK_THROWS_AS(j_nonobject.erase(0), std::domain_error);
                    CHECK_THROWS_WITH(j_nonobject.erase(0), "cannot use erase() with number");
                }
            }
        }
    }

    SECTION("object")
    {
        json j = {{"integer", 1}, {"unsigned", 1u}, {"floating", 42.23}, {"null", nullptr}, {"string", "hello world"}, {"boolean", true}, {"object", json::object()}, {"array", {1, 2, 3}}};
        const json j_const = j;

        SECTION("access specified element with bounds checking")
        {
            SECTION("access within bounds")
            {
                CHECK(j.at("integer") == json(1));
                CHECK(j.at("unsigned") == json(1u));
                CHECK(j.at("boolean") == json(true));
                CHECK(j.at("null") == json(nullptr));
                CHECK(j.at("string") == json("hello world"));
                CHECK(j.at("floating") == json(42.23));
                CHECK(j.at("object") == json(json::object()));
                CHECK(j.at("array") == json({1, 2, 3}));

                CHECK(j_const.at("integer") == json(1));
                CHECK(j_const.at("unsigned") == json(1u));
                CHECK(j_const.at("boolean") == json(true));
                CHECK(j_const.at("null") == json(nullptr));
                CHECK(j_const.at("string") == json("hello world"));
                CHECK(j_const.at("floating") == json(42.23));
                CHECK(j_const.at("object") == json(json::object()));
                CHECK(j_const.at("array") == json({1, 2, 3}));
            }

            SECTION("access outside bounds")
            {
                CHECK_THROWS_AS(j.at("foo"), std::out_of_range);
                CHECK_THROWS_AS(j_const.at("foo"), std::out_of_range);
                CHECK_THROWS_WITH(j.at("foo"), "key 'foo' not found");
                CHECK_THROWS_WITH(j_const.at("foo"), "key 'foo' not found");
            }

            SECTION("access on non-object type")
            {
                SECTION("null")
                {
                    json j_nonobject(json::value_t::null);
                    const json j_nonobject_const(j_nonobject);
                    CHECK_THROWS_AS(j_nonobject.at("foo"), std::domain_error);
                    CHECK_THROWS_AS(j_nonobject_const.at("foo"), std::domain_error);
                    CHECK_THROWS_WITH(j_nonobject.at("foo"), "cannot use at() with null");
                    CHECK_THROWS_WITH(j_nonobject_const.at("foo"), "cannot use at() with null");
                }

                SECTION("boolean")
                {
                    json j_nonobject(json::value_t::boolean);
                    const json j_nonobject_const(j_nonobject);
                    CHECK_THROWS_AS(j_nonobject.at("foo"), std::domain_error);
                    CHECK_THROWS_AS(j_nonobject_const.at("foo"), std::domain_error);
                    CHECK_THROWS_WITH(j_nonobject.at("foo"), "cannot use at() with boolean");
                    CHECK_THROWS_WITH(j_nonobject_const.at("foo"), "cannot use at() with boolean");
                }

                SECTION("string")
                {
                    json j_nonobject(json::value_t::string);
                    const json j_nonobject_const(j_nonobject);
                    CHECK_THROWS_AS(j_nonobject.at("foo"), std::domain_error);
                    CHECK_THROWS_AS(j_nonobject_const.at("foo"), std::domain_error);
                    CHECK_THROWS_WITH(j_nonobject.at("foo"), "cannot use at() with string");
                    CHECK_THROWS_WITH(j_nonobject_const.at("foo"), "cannot use at() with string");
                }

                SECTION("array")
                {
                    json j_nonobject(json::value_t::array);
                    const json j_nonobject_const(j_nonobject);
                    CHECK_THROWS_AS(j_nonobject.at("foo"), std::domain_error);
                    CHECK_THROWS_AS(j_nonobject_const.at("foo"), std::domain_error);
                    CHECK_THROWS_WITH(j_nonobject.at("foo"), "cannot use at() with array");
                    CHECK_THROWS_WITH(j_nonobject_const.at("foo"), "cannot use at() with array");
                }

                SECTION("number (integer)")
                {
                    json j_nonobject(json::value_t::number_integer);
                    const json j_nonobject_const(j_nonobject);
                    CHECK_THROWS_AS(j_nonobject.at("foo"), std::domain_error);
                    CHECK_THROWS_AS(j_nonobject_const.at("foo"), std::domain_error);
                    CHECK_THROWS_WITH(j_nonobject.at("foo"), "cannot use at() with number");
                    CHECK_THROWS_WITH(j_nonobject_const.at("foo"), "cannot use at() with number");
                }

                SECTION("number (unsigned)")
                {
                    json j_nonobject(json::value_t::number_unsigned);
                    const json j_nonobject_const(j_nonobject);
                    CHECK_THROWS_AS(j_nonobject.at("foo"), std::domain_error);
                    CHECK_THROWS_AS(j_nonobject_const.at("foo"), std::domain_error);
                    CHECK_THROWS_WITH(j_nonobject.at("foo"), "cannot use at() with number");
                    CHECK_THROWS_WITH(j_nonobject_const.at("foo"), "cannot use at() with number");
                }

                SECTION("number (floating-point)")
                {
                    json j_nonobject(json::value_t::number_float);
                    const json j_nonobject_const(j_nonobject);
                    CHECK_THROWS_AS(j_nonobject.at("foo"), std::domain_error);
                    CHECK_THROWS_AS(j_nonobject_const.at("foo"), std::domain_error);
                    CHECK_THROWS_WITH(j_nonobject.at("foo"), "cannot use at() with number");
                    CHECK_THROWS_WITH(j_nonobject_const.at("foo"), "cannot use at() with number");
                }
            }
        }

        SECTION("access specified element with default value")
        {
            SECTION("access existing value")
            {
                CHECK(j.value("integer", 2) == 1);
                CHECK(j.value("integer", 1.0) == Approx(1));
                CHECK(j.value("unsigned", 2) == 1u);
                CHECK(j.value("unsigned", 1.0) == Approx(1u));
                CHECK(j.value("null", json(1)) == json());
                CHECK(j.value("boolean", false) == true);
                CHECK(j.value("string", "bar") == "hello world");
                CHECK(j.value("string", std::string("bar")) == "hello world");
                CHECK(j.value("floating", 12.34) == Approx(42.23));
                CHECK(j.value("floating", 12) == 42);
                CHECK(j.value("object", json({{"foo", "bar"}})) == json(json::object()));
                CHECK(j.value("array", json({10, 100})) == json({1, 2, 3}));

                CHECK(j_const.value("integer", 2) == 1);
                CHECK(j_const.value("integer", 1.0) == Approx(1));
                CHECK(j_const.value("unsigned", 2) == 1u);
                CHECK(j_const.value("unsigned", 1.0) == Approx(1u));
                CHECK(j_const.value("boolean", false) == true);
                CHECK(j_const.value("string", "bar") == "hello world");
                CHECK(j_const.value("string", std::string("bar")) == "hello world");
                CHECK(j_const.value("floating", 12.34) == Approx(42.23));
                CHECK(j_const.value("floating", 12) == 42);
                CHECK(j_const.value("object", json({{"foo", "bar"}})) == json(json::object()));
                CHECK(j_const.value("array", json({10, 100})) == json({1, 2, 3}));
            }

            SECTION("access non-existing value")
            {
                CHECK(j.value("_", 2) == 2);
                CHECK(j.value("_", 2u) == 2u);
                CHECK(j.value("_", false) == false);
                CHECK(j.value("_", "bar") == "bar");
                CHECK(j.value("_", 12.34) == Approx(12.34));
                CHECK(j.value("_", json({{"foo", "bar"}})) == json({{"foo", "bar"}}));
                CHECK(j.value("_", json({10, 100})) == json({10, 100}));

                CHECK(j_const.value("_", 2) == 2);
                CHECK(j_const.value("_", 2u) == 2u);
                CHECK(j_const.value("_", false) == false);
                CHECK(j_const.value("_", "bar") == "bar");
                CHECK(j_const.value("_", 12.34) == Approx(12.34));
                CHECK(j_const.value("_", json({{"foo", "bar"}})) == json({{"foo", "bar"}}));
                CHECK(j_const.value("_", json({10, 100})) == json({10, 100}));
            }

            SECTION("access on non-object type")
            {
                SECTION("null")
                {
                    json j_nonobject(json::value_t::null);
                    const json j_nonobject_const(j_nonobject);
                    CHECK_THROWS_AS(j_nonobject.value("foo", 1), std::domain_error);
                    CHECK_THROWS_AS(j_nonobject_const.value("foo", 1), std::domain_error);
                    CHECK_THROWS_WITH(j_nonobject.value("foo", 1), "cannot use value() with null");
                    CHECK_THROWS_WITH(j_nonobject_const.value("foo", 1), "cannot use value() with null");
                }

                SECTION("boolean")
                {
                    json j_nonobject(json::value_t::boolean);
                    const json j_nonobject_const(j_nonobject);
                    CHECK_THROWS_AS(j_nonobject.value("foo", 1), std::domain_error);
                    CHECK_THROWS_AS(j_nonobject_const.value("foo", 1), std::domain_error);
                    CHECK_THROWS_WITH(j_nonobject.value("foo", 1), "cannot use value() with boolean");
                    CHECK_THROWS_WITH(j_nonobject_const.value("foo", 1), "cannot use value() with boolean");
                }

                SECTION("string")
                {
                    json j_nonobject(json::value_t::string);
                    const json j_nonobject_const(j_nonobject);
                    CHECK_THROWS_AS(j_nonobject.value("foo", 1), std::domain_error);
                    CHECK_THROWS_AS(j_nonobject_const.value("foo", 1), std::domain_error);
                    CHECK_THROWS_WITH(j_nonobject.value("foo", 1), "cannot use value() with string");
                    CHECK_THROWS_WITH(j_nonobject_const.value("foo", 1), "cannot use value() with string");
                }

                SECTION("array")
                {
                    json j_nonobject(json::value_t::array);
                    const json j_nonobject_const(j_nonobject);
                    CHECK_THROWS_AS(j_nonobject.value("foo", 1), std::domain_error);
                    CHECK_THROWS_AS(j_nonobject_const.value("foo", 1), std::domain_error);
                    CHECK_THROWS_WITH(j_nonobject.value("foo", 1), "cannot use value() with array");
                    CHECK_THROWS_WITH(j_nonobject_const.value("foo", 1), "cannot use value() with array");
                }

                SECTION("number (integer)")
                {
                    json j_nonobject(json::value_t::number_integer);
                    const json j_nonobject_const(j_nonobject);
                    CHECK_THROWS_AS(j_nonobject.value("foo", 1), std::domain_error);
                    CHECK_THROWS_AS(j_nonobject_const.value("foo", 1), std::domain_error);
                    CHECK_THROWS_WITH(j_nonobject.value("foo", 1), "cannot use value() with number");
                    CHECK_THROWS_WITH(j_nonobject_const.value("foo", 1), "cannot use value() with number");
                }

                SECTION("number (unsigned)")
                {
                    json j_nonobject(json::value_t::number_unsigned);
                    const json j_nonobject_const(j_nonobject);
                    CHECK_THROWS_AS(j_nonobject.value("foo", 1), std::domain_error);
                    CHECK_THROWS_AS(j_nonobject_const.value("foo", 1), std::domain_error);
                    CHECK_THROWS_WITH(j_nonobject.value("foo", 1), "cannot use value() with number");
                    CHECK_THROWS_WITH(j_nonobject_const.value("foo", 1), "cannot use value() with number");
                }

                SECTION("number (floating-point)")
                {
                    json j_nonobject(json::value_t::number_float);
                    const json j_nonobject_const(j_nonobject);
                    CHECK_THROWS_AS(j_nonobject.value("foo", 1), std::domain_error);
                    CHECK_THROWS_AS(j_nonobject_const.value("foo", 1), std::domain_error);
                    CHECK_THROWS_WITH(j_nonobject.value("foo", 1), "cannot use value() with number");
                    CHECK_THROWS_WITH(j_nonobject_const.value("foo", 1), "cannot use value() with number");
                }
            }
        }

        SECTION("front and back")
        {
            // "array" is the smallest key
            CHECK(j.front() == json({1, 2, 3}));
            CHECK(j_const.front() == json({1, 2, 3}));
            // "unsigned" is the largest key
            CHECK(j.back() == json(1u));
            CHECK(j_const.back() == json(1u));
        }

        SECTION("access specified element")
        {
            SECTION("access within bounds")
            {
                CHECK(j["integer"] == json(1));
                CHECK(j[json::object_t::key_type("integer")] == j["integer"]);

                CHECK(j["unsigned"] == json(1u));
                CHECK(j[json::object_t::key_type("unsigned")] == j["unsigned"]);

                CHECK(j["boolean"] == json(true));
                CHECK(j[json::object_t::key_type("boolean")] == j["boolean"]);

                CHECK(j["null"] == json(nullptr));
                CHECK(j[json::object_t::key_type("null")] == j["null"]);

                CHECK(j["string"] == json("hello world"));
                CHECK(j[json::object_t::key_type("string")] == j["string"]);

                CHECK(j["floating"] == json(42.23));
                CHECK(j[json::object_t::key_type("floating")] == j["floating"]);

                CHECK(j["object"] == json(json::object()));
                CHECK(j[json::object_t::key_type("object")] == j["object"]);

                CHECK(j["array"] == json({1, 2, 3}));
                CHECK(j[json::object_t::key_type("array")] == j["array"]);

                CHECK(j_const["integer"] == json(1));
                CHECK(j_const[json::object_t::key_type("integer")] == j["integer"]);

                CHECK(j_const["boolean"] == json(true));
                CHECK(j_const[json::object_t::key_type("boolean")] == j["boolean"]);

                CHECK(j_const["null"] == json(nullptr));
                CHECK(j_const[json::object_t::key_type("null")] == j["null"]);

                CHECK(j_const["string"] == json("hello world"));
                CHECK(j_const[json::object_t::key_type("string")] == j["string"]);

                CHECK(j_const["floating"] == json(42.23));
                CHECK(j_const[json::object_t::key_type("floating")] == j["floating"]);

                CHECK(j_const["object"] == json(json::object()));
                CHECK(j_const[json::object_t::key_type("object")] == j["object"]);

                CHECK(j_const["array"] == json({1, 2, 3}));
                CHECK(j_const[json::object_t::key_type("array")] == j["array"]);
            }

            SECTION("access on non-object type")
            {
                SECTION("null")
                {
                    json j_nonobject(json::value_t::null);
                    json j_nonobject2(json::value_t::null);
                    const json j_const_nonobject(j_nonobject);
                    CHECK_NOTHROW(j_nonobject["foo"]);
                    CHECK_NOTHROW(j_nonobject2[json::object_t::key_type("foo")]);
                    CHECK_THROWS_AS(j_const_nonobject["foo"], std::domain_error);
                    CHECK_THROWS_AS(j_const_nonobject[json::object_t::key_type("foo")], std::domain_error);
                    CHECK_THROWS_WITH(j_const_nonobject["foo"], "cannot use operator[] with null");
                    CHECK_THROWS_WITH(j_const_nonobject[json::object_t::key_type("foo")],
                                      "cannot use operator[] with null");
                }

                SECTION("boolean")
                {
                    json j_nonobject(json::value_t::boolean);
                    const json j_const_nonobject(j_nonobject);
                    CHECK_THROWS_AS(j_nonobject["foo"], std::domain_error);
                    CHECK_THROWS_AS(j_nonobject[json::object_t::key_type("foo")], std::domain_error);
                    CHECK_THROWS_AS(j_const_nonobject["foo"], std::domain_error);
                    CHECK_THROWS_AS(j_const_nonobject[json::object_t::key_type("foo")], std::domain_error);
                    CHECK_THROWS_WITH(j_nonobject["foo"], "cannot use operator[] with boolean");
                    CHECK_THROWS_WITH(j_nonobject[json::object_t::key_type("foo")],
                                      "cannot use operator[] with boolean");
                    CHECK_THROWS_WITH(j_const_nonobject["foo"], "cannot use operator[] with boolean");
                    CHECK_THROWS_WITH(j_const_nonobject[json::object_t::key_type("foo")],
                                      "cannot use operator[] with boolean");
                }

                SECTION("string")
                {
                    json j_nonobject(json::value_t::string);
                    const json j_const_nonobject(j_nonobject);
                    CHECK_THROWS_AS(j_nonobject["foo"], std::domain_error);
                    CHECK_THROWS_AS(j_nonobject[json::object_t::key_type("foo")], std::domain_error);
                    CHECK_THROWS_AS(j_const_nonobject["foo"], std::domain_error);
                    CHECK_THROWS_AS(j_const_nonobject[json::object_t::key_type("foo")], std::domain_error);
                    CHECK_THROWS_WITH(j_nonobject["foo"], "cannot use operator[] with string");
                    CHECK_THROWS_WITH(j_nonobject[json::object_t::key_type("foo")],
                                      "cannot use operator[] with string");
                    CHECK_THROWS_WITH(j_const_nonobject["foo"], "cannot use operator[] with string");
                    CHECK_THROWS_WITH(j_const_nonobject[json::object_t::key_type("foo")],
                                      "cannot use operator[] with string");
                }

                SECTION("array")
                {
                    json j_nonobject(json::value_t::array);
                    const json j_const_nonobject(j_nonobject);
                    CHECK_THROWS_AS(j_nonobject["foo"], std::domain_error);
                    CHECK_THROWS_AS(j_nonobject[json::object_t::key_type("foo")], std::domain_error);
                    CHECK_THROWS_AS(j_const_nonobject["foo"], std::domain_error);
                    CHECK_THROWS_AS(j_const_nonobject[json::object_t::key_type("foo")], std::domain_error);
                    CHECK_THROWS_WITH(j_nonobject["foo"], "cannot use operator[] with array");
                    CHECK_THROWS_WITH(j_nonobject[json::object_t::key_type("foo")], "cannot use operator[] with array");
                    CHECK_THROWS_WITH(j_const_nonobject["foo"], "cannot use operator[] with array");
                    CHECK_THROWS_WITH(j_const_nonobject[json::object_t::key_type("foo")],
                                      "cannot use operator[] with array");
                }

                SECTION("number (integer)")
                {
                    json j_nonobject(json::value_t::number_integer);
                    const json j_const_nonobject(j_nonobject);
                    CHECK_THROWS_AS(j_nonobject["foo"], std::domain_error);
                    CHECK_THROWS_AS(j_nonobject[json::object_t::key_type("foo")], std::domain_error);
                    CHECK_THROWS_AS(j_const_nonobject["foo"], std::domain_error);
                    CHECK_THROWS_AS(j_const_nonobject[json::object_t::key_type("foo")], std::domain_error);
                    CHECK_THROWS_WITH(j_nonobject["foo"], "cannot use operator[] with number");
                    CHECK_THROWS_WITH(j_nonobject[json::object_t::key_type("foo")],
                                      "cannot use operator[] with number");
                    CHECK_THROWS_WITH(j_const_nonobject["foo"], "cannot use operator[] with number");
                    CHECK_THROWS_WITH(j_const_nonobject[json::object_t::key_type("foo")],
                                      "cannot use operator[] with number");
                }

                SECTION("number (unsigned)")
                {
                    json j_nonobject(json::value_t::number_unsigned);
                    const json j_const_nonobject(j_nonobject);
                    CHECK_THROWS_AS(j_nonobject["foo"], std::domain_error);
                    CHECK_THROWS_AS(j_nonobject[json::object_t::key_type("foo")], std::domain_error);
                    CHECK_THROWS_AS(j_const_nonobject["foo"], std::domain_error);
                    CHECK_THROWS_AS(j_const_nonobject[json::object_t::key_type("foo")], std::domain_error);
                    CHECK_THROWS_WITH(j_nonobject["foo"], "cannot use operator[] with number");
                    CHECK_THROWS_WITH(j_nonobject[json::object_t::key_type("foo")],
                                      "cannot use operator[] with number");
                    CHECK_THROWS_WITH(j_const_nonobject["foo"], "cannot use operator[] with number");
                    CHECK_THROWS_WITH(j_const_nonobject[json::object_t::key_type("foo")],
                                      "cannot use operator[] with number");
                }

                SECTION("number (floating-point)")
                {
                    json j_nonobject(json::value_t::number_float);
                    const json j_const_nonobject(j_nonobject);
                    CHECK_THROWS_AS(j_nonobject["foo"], std::domain_error);
                    CHECK_THROWS_AS(j_nonobject[json::object_t::key_type("foo")], std::domain_error);
                    CHECK_THROWS_AS(j_const_nonobject["foo"], std::domain_error);
                    CHECK_THROWS_AS(j_const_nonobject[json::object_t::key_type("foo")], std::domain_error);
                    CHECK_THROWS_WITH(j_nonobject["foo"], "cannot use operator[] with number");
                    CHECK_THROWS_WITH(j_nonobject[json::object_t::key_type("foo")],
                                      "cannot use operator[] with number");
                    CHECK_THROWS_WITH(j_const_nonobject["foo"], "cannot use operator[] with number");
                    CHECK_THROWS_WITH(j_const_nonobject[json::object_t::key_type("foo")],
                                      "cannot use operator[] with number");
                }
            }
        }

        SECTION("remove specified element")
        {
            SECTION("remove element by key")
            {
                CHECK(j.find("integer") != j.end());
                CHECK(j.erase("integer") == 1);
                CHECK(j.find("integer") == j.end());
                CHECK(j.erase("integer") == 0);

                CHECK(j.find("unsigned") != j.end());
                CHECK(j.erase("unsigned") == 1);
                CHECK(j.find("unsigned") == j.end());
                CHECK(j.erase("unsigned") == 0);

                CHECK(j.find("boolean") != j.end());
                CHECK(j.erase("boolean") == 1);
                CHECK(j.find("boolean") == j.end());
                CHECK(j.erase("boolean") == 0);

                CHECK(j.find("null") != j.end());
                CHECK(j.erase("null") == 1);
                CHECK(j.find("null") == j.end());
                CHECK(j.erase("null") == 0);

                CHECK(j.find("string") != j.end());
                CHECK(j.erase("string") == 1);
                CHECK(j.find("string") == j.end());
                CHECK(j.erase("string") == 0);

                CHECK(j.find("floating") != j.end());
                CHECK(j.erase("floating") == 1);
                CHECK(j.find("floating") == j.end());
                CHECK(j.erase("floating") == 0);

                CHECK(j.find("object") != j.end());
                CHECK(j.erase("object") == 1);
                CHECK(j.find("object") == j.end());
                CHECK(j.erase("object") == 0);

                CHECK(j.find("array") != j.end());
                CHECK(j.erase("array") == 1);
                CHECK(j.find("array") == j.end());
                CHECK(j.erase("array") == 0);
            }

            SECTION("remove element by iterator")
            {
                SECTION("erase(begin())")
                {
                    {
                        json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
                        json::iterator it2 = jobject.erase(jobject.begin());
                        CHECK(jobject == json({{"b", 1}, {"c", 17u}}));
                        CHECK(*it2 == json(1));
                    }
                    {
                        json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
                        json::const_iterator it2 = jobject.erase(jobject.cbegin());
                        CHECK(jobject == json({{"b", 1}, {"c", 17u}}));
                        CHECK(*it2 == json(1));
                    }
                }

                SECTION("erase(begin(), end())")
                {
                    {
                        json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
                        json::iterator it2 = jobject.erase(jobject.begin(), jobject.end());
                        CHECK(jobject == json::object());
                        CHECK(it2 == jobject.end());
                    }
                    {
                        json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
                        json::const_iterator it2 = jobject.erase(jobject.cbegin(), jobject.cend());
                        CHECK(jobject == json::object());
                        CHECK(it2 == jobject.cend());
                    }
                }

                SECTION("erase(begin(), begin())")
                {
                    {
                        json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
                        json::iterator it2 = jobject.erase(jobject.begin(), jobject.begin());
                        CHECK(jobject == json({{"a", "a"}, {"b", 1}, {"c", 17u}}));
                        CHECK(*it2 == json("a"));
                    }
                    {
                        json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
                        json::const_iterator it2 = jobject.erase(jobject.cbegin(), jobject.cbegin());
                        CHECK(jobject == json({{"a", "a"}, {"b", 1}, {"c", 17u}}));
                        CHECK(*it2 == json("a"));
                    }
                }

                SECTION("erase at offset")
                {
                    {
                        json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
                        json::iterator it = jobject.find("b");
                        json::iterator it2 = jobject.erase(it);
                        CHECK(jobject == json({{"a", "a"}, {"c", 17u}}));
                        CHECK(*it2 == json(17));
                    }
                    {
                        json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
                        json::const_iterator it = jobject.find("b");
                        json::const_iterator it2 = jobject.erase(it);
                        CHECK(jobject == json({{"a", "a"}, {"c", 17u}}));
                        CHECK(*it2 == json(17));
                    }
                }

                SECTION("erase subrange")
                {
                    {
                        json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}, {"d", false}, {"e", true}};
                        json::iterator it2 = jobject.erase(jobject.find("b"), jobject.find("e"));
                        CHECK(jobject == json({{"a", "a"}, {"e", true}}));
                        CHECK(*it2 == json(true));
                    }
                    {
                        json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}, {"d", false}, {"e", true}};
                        json::const_iterator it2 = jobject.erase(jobject.find("b"), jobject.find("e"));
                        CHECK(jobject == json({{"a", "a"}, {"e", true}}));
                        CHECK(*it2 == json(true));
                    }
                }

                SECTION("different objects")
                {
                    {
                        json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}, {"d", false}, {"e", true}};
                        json jobject2 = {{"a", "a"}, {"b", 1}, {"c", 17u}};
                        CHECK_THROWS_AS(jobject.erase(jobject2.begin()), std::domain_error);
                        CHECK_THROWS_AS(jobject.erase(jobject.begin(), jobject2.end()), std::domain_error);
                        CHECK_THROWS_AS(jobject.erase(jobject2.begin(), jobject.end()), std::domain_error);
                        CHECK_THROWS_AS(jobject.erase(jobject2.begin(), jobject2.end()), std::domain_error);
                        CHECK_THROWS_WITH(jobject.erase(jobject2.begin()), "iterator does not fit current value");
                        CHECK_THROWS_WITH(jobject.erase(jobject.begin(), jobject2.end()),
                                          "iterators do not fit current value");
                        CHECK_THROWS_WITH(jobject.erase(jobject2.begin(), jobject.end()),
                                          "iterators do not fit current value");
                        CHECK_THROWS_WITH(jobject.erase(jobject2.begin(), jobject2.end()),
                                          "iterators do not fit current value");
                    }
                    {
                        json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}, {"d", false}, {"e", true}};
                        json jobject2 = {{"a", "a"}, {"b", 1}, {"c", 17u}};
                        CHECK_THROWS_AS(jobject.erase(jobject2.cbegin()), std::domain_error);
                        CHECK_THROWS_AS(jobject.erase(jobject.cbegin(), jobject2.cend()), std::domain_error);
                        CHECK_THROWS_AS(jobject.erase(jobject2.cbegin(), jobject.cend()), std::domain_error);
                        CHECK_THROWS_AS(jobject.erase(jobject2.cbegin(), jobject2.cend()), std::domain_error);
                        CHECK_THROWS_WITH(jobject.erase(jobject2.cbegin()), "iterator does not fit current value");
                        CHECK_THROWS_WITH(jobject.erase(jobject.cbegin(), jobject2.cend()),
                                          "iterators do not fit current value");
                        CHECK_THROWS_WITH(jobject.erase(jobject2.cbegin(), jobject.cend()),
                                          "iterators do not fit current value");
                        CHECK_THROWS_WITH(jobject.erase(jobject2.cbegin(), jobject2.cend()),
                                          "iterators do not fit current value");
                    }
                }
            }

            SECTION("remove element by key in non-object type")
            {
                SECTION("null")
                {
                    json j_nonobject(json::value_t::null);
                    CHECK_THROWS_AS(j_nonobject.erase("foo"), std::domain_error);
                    CHECK_THROWS_WITH(j_nonobject.erase("foo"), "cannot use erase() with null");
                }

                SECTION("boolean")
                {
                    json j_nonobject(json::value_t::boolean);
                    CHECK_THROWS_AS(j_nonobject.erase("foo"), std::domain_error);
                    CHECK_THROWS_WITH(j_nonobject.erase("foo"), "cannot use erase() with boolean");
                }

                SECTION("string")
                {
                    json j_nonobject(json::value_t::string);
                    CHECK_THROWS_AS(j_nonobject.erase("foo"), std::domain_error);
                    CHECK_THROWS_WITH(j_nonobject.erase("foo"), "cannot use erase() with string");
                }

                SECTION("array")
                {
                    json j_nonobject(json::value_t::array);
                    CHECK_THROWS_AS(j_nonobject.erase("foo"), std::domain_error);
                    CHECK_THROWS_WITH(j_nonobject.erase("foo"), "cannot use erase() with array");
                }

                SECTION("number (integer)")
                {
                    json j_nonobject(json::value_t::number_integer);
                    CHECK_THROWS_AS(j_nonobject.erase("foo"), std::domain_error);
                    CHECK_THROWS_WITH(j_nonobject.erase("foo"), "cannot use erase() with number");
                }

                SECTION("number (floating-point)")
                {
                    json j_nonobject(json::value_t::number_float);
                    CHECK_THROWS_AS(j_nonobject.erase("foo"), std::domain_error);
                    CHECK_THROWS_WITH(j_nonobject.erase("foo"), "cannot use erase() with number");
                }
            }
        }

        SECTION("find an element in an object")
        {
            SECTION("existing element")
            {
                for (auto key :
                        {"integer", "unsigned", "floating", "null", "string", "boolean", "object", "array"
                        })
                {
                    CHECK(j.find(key) != j.end());
                    CHECK(*j.find(key) == j.at(key));
                    CHECK(j_const.find(key) != j_const.end());
                    CHECK(*j_const.find(key) == j_const.at(key));
                }
            }

            SECTION("nonexisting element")
            {
                CHECK(j.find("foo") == j.end());
                CHECK(j_const.find("foo") == j_const.end());
            }

            SECTION("all types")
            {
                SECTION("null")
                {
                    json j_nonarray(json::value_t::null);
                    const json j_nonarray_const(j_nonarray);
                    CHECK(j_nonarray.find("foo") == j_nonarray.end());
                    CHECK(j_nonarray_const.find("foo") == j_nonarray_const.end());
                }

                SECTION("string")
                {
                    json j_nonarray(json::value_t::string);
                    const json j_nonarray_const(j_nonarray);
                    CHECK(j_nonarray.find("foo") == j_nonarray.end());
                    CHECK(j_nonarray_const.find("foo") == j_nonarray_const.end());
                }

                SECTION("object")
                {
                    json j_nonarray(json::value_t::object);
                    const json j_nonarray_const(j_nonarray);
                    CHECK(j_nonarray.find("foo") == j_nonarray.end());
                    CHECK(j_nonarray_const.find("foo") == j_nonarray_const.end());
                }

                SECTION("array")
                {
                    json j_nonarray(json::value_t::array);
                    const json j_nonarray_const(j_nonarray);
                    CHECK(j_nonarray.find("foo") == j_nonarray.end());
                    CHECK(j_nonarray_const.find("foo") == j_nonarray_const.end());
                }

                SECTION("boolean")
                {
                    json j_nonarray(json::value_t::boolean);
                    const json j_nonarray_const(j_nonarray);
                    CHECK(j_nonarray.find("foo") == j_nonarray.end());
                    CHECK(j_nonarray_const.find("foo") == j_nonarray_const.end());
                }

                SECTION("number (integer)")
                {
                    json j_nonarray(json::value_t::number_integer);
                    const json j_nonarray_const(j_nonarray);
                    CHECK(j_nonarray.find("foo") == j_nonarray.end());
                    CHECK(j_nonarray_const.find("foo") == j_nonarray_const.end());
                }

                SECTION("number (unsigned)")
                {
                    json j_nonarray(json::value_t::number_unsigned);
                    const json j_nonarray_const(j_nonarray);
                    CHECK(j_nonarray.find("foo") == j_nonarray.end());
                    CHECK(j_nonarray_const.find("foo") == j_nonarray_const.end());
                }

                SECTION("number (floating-point)")
                {
                    json j_nonarray(json::value_t::number_float);
                    const json j_nonarray_const(j_nonarray);
                    CHECK(j_nonarray.find("foo") == j_nonarray.end());
                    CHECK(j_nonarray_const.find("foo") == j_nonarray_const.end());
                }
            }
        }

        SECTION("count keys in an object")
        {
            SECTION("existing element")
            {
                for (auto key :
                        {"integer", "unsigned", "floating", "null", "string", "boolean", "object", "array"
                        })
                {
                    CHECK(j.count(key) == 1);
                    CHECK(j_const.count(key) == 1);
                }
            }

            SECTION("nonexisting element")
            {
                CHECK(j.count("foo") == 0);
                CHECK(j_const.count("foo") == 0);
            }

            SECTION("all types")
            {
                SECTION("null")
                {
                    json j_nonobject(json::value_t::null);
                    const json j_nonobject_const(j_nonobject);
                    CHECK(j_nonobject.count("foo") == 0);
                    CHECK(j_nonobject_const.count("foo") == 0);
                }

                SECTION("string")
                {
                    json j_nonobject(json::value_t::string);
                    const json j_nonobject_const(j_nonobject);
                    CHECK(j_nonobject.count("foo") == 0);
                    CHECK(j_nonobject_const.count("foo") == 0);
                }

                SECTION("object")
                {
                    json j_nonobject(json::value_t::object);
                    const json j_nonobject_const(j_nonobject);
                    CHECK(j_nonobject.count("foo") == 0);
                    CHECK(j_nonobject_const.count("foo") == 0);
                }

                SECTION("array")
                {
                    json j_nonobject(json::value_t::array);
                    const json j_nonobject_const(j_nonobject);
                    CHECK(j_nonobject.count("foo") == 0);
                    CHECK(j_nonobject_const.count("foo") == 0);
                }

                SECTION("boolean")
                {
                    json j_nonobject(json::value_t::boolean);
                    const json j_nonobject_const(j_nonobject);
                    CHECK(j_nonobject.count("foo") == 0);
                    CHECK(j_nonobject_const.count("foo") == 0);
                }

                SECTION("number (integer)")
                {
                    json j_nonobject(json::value_t::number_integer);
                    const json j_nonobject_const(j_nonobject);
                    CHECK(j_nonobject.count("foo") == 0);
                    CHECK(j_nonobject_const.count("foo") == 0);
                }

                SECTION("number (unsigned)")
                {
                    json j_nonobject(json::value_t::number_unsigned);
                    const json j_nonobject_const(j_nonobject);
                    CHECK(j_nonobject.count("foo") == 0);
                    CHECK(j_nonobject_const.count("foo") == 0);
                }

                SECTION("number (floating-point)")
                {
                    json j_nonobject(json::value_t::number_float);
                    const json j_nonobject_const(j_nonobject);
                    CHECK(j_nonobject.count("foo") == 0);
                    CHECK(j_nonobject_const.count("foo") == 0);
                }
            }
        }
    }

    SECTION("other values")
    {
        SECTION("front and back")
        {
            SECTION("null")
            {
                {
                    json j;
                    CHECK_THROWS_AS(j.front(), std::out_of_range);
                    CHECK_THROWS_AS(j.back(), std::out_of_range);
                    CHECK_THROWS_WITH(j.front(), "cannot get value");
                    CHECK_THROWS_WITH(j.back(), "cannot get value");
                }
                {
                    const json j{};
                    CHECK_THROWS_AS(j.front(), std::out_of_range);
                    CHECK_THROWS_AS(j.back(), std::out_of_range);
                    CHECK_THROWS_WITH(j.front(), "cannot get value");
                    CHECK_THROWS_WITH(j.back(), "cannot get value");
                }
            }

            SECTION("string")
            {
                {
                    json j = "foo";
                    CHECK(j.front() == j);
                    CHECK(j.back() == j);
                }
                {
                    const json j = "bar";
                    CHECK(j.front() == j);
                    CHECK(j.back() == j);
                }
            }

            SECTION("number (boolean)")
            {
                {
                    json j = false;
                    CHECK(j.front() == j);
                    CHECK(j.back() == j);
                }
                {
                    const json j = true;
                    CHECK(j.front() == j);
                    CHECK(j.back() == j);
                }
            }

            SECTION("number (integer)")
            {
                {
                    json j = 17;
                    CHECK(j.front() == j);
                    CHECK(j.back() == j);
                }
                {
                    const json j = 17;
                    CHECK(j.front() == j);
                    CHECK(j.back() == j);
                }
            }

            SECTION("number (unsigned)")
            {
                {
                    json j = 17u;
                    CHECK(j.front() == j);
                    CHECK(j.back() == j);
                }
                {
                    const json j = 17u;
                    CHECK(j.front() == j);
                    CHECK(j.back() == j);
                }
            }

            SECTION("number (floating point)")
            {
                {
                    json j = 23.42;
                    CHECK(j.front() == j);
                    CHECK(j.back() == j);
                }
                {
                    const json j = 23.42;
                    CHECK(j.front() == j);
                    CHECK(j.back() == j);
                }
            }
        }

        SECTION("erase with one valid iterator")
        {
            SECTION("null")
            {
                {
                    json j;
                    CHECK_THROWS_AS(j.erase(j.begin()), std::domain_error);
                    CHECK_THROWS_WITH(j.erase(j.begin()), "cannot use erase() with null");
                }
                {
                    json j;
                    CHECK_THROWS_AS(j.erase(j.cbegin()), std::domain_error);
                    CHECK_THROWS_WITH(j.erase(j.begin()), "cannot use erase() with null");
                }
            }

            SECTION("string")
            {
                {
                    json j = "foo";
                    json::iterator it = j.erase(j.begin());
                    CHECK(j.type() == json::value_t::null);
                    CHECK(it == j.end());
                }
                {
                    json j = "bar";
                    json::const_iterator it = j.erase(j.cbegin());
                    CHECK(j.type() == json::value_t::null);
                    CHECK(it == j.end());
                }
            }

            SECTION("number (boolean)")
            {
                {
                    json j = false;
                    json::iterator it = j.erase(j.begin());
                    CHECK(j.type() == json::value_t::null);
                    CHECK(it == j.end());
                }
                {
                    json j = true;
                    json::const_iterator it = j.erase(j.cbegin());
                    CHECK(j.type() == json::value_t::null);
                    CHECK(it == j.end());
                }
            }

            SECTION("number (integer)")
            {
                {
                    json j = 17;
                    json::iterator it = j.erase(j.begin());
                    CHECK(j.type() == json::value_t::null);
                    CHECK(it == j.end());
                }
                {
                    json j = 17;
                    json::const_iterator it = j.erase(j.cbegin());
                    CHECK(j.type() == json::value_t::null);
                    CHECK(it == j.end());
                }
            }

            SECTION("number (unsigned)")
            {
                {
                    json j = 17u;
                    json::iterator it = j.erase(j.begin());
                    CHECK(j.type() == json::value_t::null);
                    CHECK(it == j.end());
                }
                {
                    json j = 17u;
                    json::const_iterator it = j.erase(j.cbegin());
                    CHECK(j.type() == json::value_t::null);
                    CHECK(it == j.end());
                }
            }

            SECTION("number (floating point)")
            {
                {
                    json j = 23.42;
                    json::iterator it = j.erase(j.begin());
                    CHECK(j.type() == json::value_t::null);
                    CHECK(it == j.end());
                }
                {
                    json j = 23.42;
                    json::const_iterator it = j.erase(j.cbegin());
                    CHECK(j.type() == json::value_t::null);
                    CHECK(it == j.end());
                }
            }
        }

        SECTION("erase with one invalid iterator")
        {
            SECTION("string")
            {
                {
                    json j = "foo";
                    CHECK_THROWS_AS(j.erase(j.end()), std::out_of_range);
                    CHECK_THROWS_WITH(j.erase(j.end()), "iterator out of range");
                }
                {
                    json j = "bar";
                    CHECK_THROWS_AS(j.erase(j.cend()), std::out_of_range);
                    CHECK_THROWS_WITH(j.erase(j.cend()), "iterator out of range");
                }
            }

            SECTION("number (boolean)")
            {
                {
                    json j = false;
                    CHECK_THROWS_AS(j.erase(j.end()), std::out_of_range);
                    CHECK_THROWS_WITH(j.erase(j.end()), "iterator out of range");
                }
                {
                    json j = true;
                    CHECK_THROWS_AS(j.erase(j.cend()), std::out_of_range);
                    CHECK_THROWS_WITH(j.erase(j.cend()), "iterator out of range");
                }
            }

            SECTION("number (integer)")
            {
                {
                    json j = 17;
                    CHECK_THROWS_AS(j.erase(j.end()), std::out_of_range);
                    CHECK_THROWS_WITH(j.erase(j.end()), "iterator out of range");
                }
                {
                    json j = 17;
                    CHECK_THROWS_AS(j.erase(j.cend()), std::out_of_range);
                    CHECK_THROWS_WITH(j.erase(j.cend()), "iterator out of range");
                }
            }

            SECTION("number (unsigned)")
            {
                {
                    json j = 17u;
                    CHECK_THROWS_AS(j.erase(j.end()), std::out_of_range);
                    CHECK_THROWS_WITH(j.erase(j.end()), "iterator out of range");
                }
                {
                    json j = 17u;
                    CHECK_THROWS_AS(j.erase(j.cend()), std::out_of_range);
                    CHECK_THROWS_WITH(j.erase(j.cend()), "iterator out of range");
                }
            }

            SECTION("number (floating point)")
            {
                {
                    json j = 23.42;
                    CHECK_THROWS_AS(j.erase(j.end()), std::out_of_range);
                    CHECK_THROWS_WITH(j.erase(j.end()), "iterator out of range");
                }
                {
                    json j = 23.42;
                    CHECK_THROWS_AS(j.erase(j.cend()), std::out_of_range);
                    CHECK_THROWS_WITH(j.erase(j.cend()), "iterator out of range");
                }
            }
        }

        SECTION("erase with two valid iterators")
        {
            SECTION("null")
            {
                {
                    json j;
                    CHECK_THROWS_AS(j.erase(j.begin(), j.end()), std::domain_error);
                    CHECK_THROWS_WITH(j.erase(j.begin(), j.end()), "cannot use erase() with null");
                }
                {
                    json j;
                    CHECK_THROWS_AS(j.erase(j.cbegin(), j.cend()), std::domain_error);
                    CHECK_THROWS_WITH(j.erase(j.cbegin(), j.cend()), "cannot use erase() with null");
                }
            }

            SECTION("string")
            {
                {
                    json j = "foo";
                    json::iterator it = j.erase(j.begin(), j.end());
                    CHECK(j.type() == json::value_t::null);
                    CHECK(it == j.end());
                }
                {
                    json j = "bar";
                    json::const_iterator it = j.erase(j.cbegin(), j.cend());
                    CHECK(j.type() == json::value_t::null);
                    CHECK(it == j.end());
                }
            }

            SECTION("number (boolean)")
            {
                {
                    json j = false;
                    json::iterator it = j.erase(j.begin(), j.end());
                    CHECK(j.type() == json::value_t::null);
                    CHECK(it == j.end());
                }
                {
                    json j = true;
                    json::const_iterator it = j.erase(j.cbegin(), j.cend());
                    CHECK(j.type() == json::value_t::null);
                    CHECK(it == j.end());
                }
            }

            SECTION("number (integer)")
            {
                {
                    json j = 17;
                    json::iterator it = j.erase(j.begin(), j.end());
                    CHECK(j.type() == json::value_t::null);
                    CHECK(it == j.end());
                }
                {
                    json j = 17;
                    json::const_iterator it = j.erase(j.cbegin(), j.cend());
                    CHECK(j.type() == json::value_t::null);
                    CHECK(it == j.end());
                }
            }

            SECTION("number (unsigned)")
            {
                {
                    json j = 17u;
                    json::iterator it = j.erase(j.begin(), j.end());
                    CHECK(j.type() == json::value_t::null);
                    CHECK(it == j.end());
                }
                {
                    json j = 17u;
                    json::const_iterator it = j.erase(j.cbegin(), j.cend());
                    CHECK(j.type() == json::value_t::null);
                    CHECK(it == j.end());
                }
            }

            SECTION("number (floating point)")
            {
                {
                    json j = 23.42;
                    json::iterator it = j.erase(j.begin(), j.end());
                    CHECK(j.type() == json::value_t::null);
                    CHECK(it == j.end());
                }
                {
                    json j = 23.42;
                    json::const_iterator it = j.erase(j.cbegin(), j.cend());
                    CHECK(j.type() == json::value_t::null);
                    CHECK(it == j.end());
                }
            }
        }

        SECTION("erase with two invalid iterators")
        {
            SECTION("string")
            {
                {
                    json j = "foo";
                    CHECK_THROWS_AS(j.erase(j.end(), j.end()), std::out_of_range);
                    CHECK_THROWS_AS(j.erase(j.begin(), j.begin()), std::out_of_range);
                    CHECK_THROWS_WITH(j.erase(j.end(), j.end()), "iterators out of range");
                    CHECK_THROWS_WITH(j.erase(j.begin(), j.begin()), "iterators out of range");
                }
                {
                    json j = "bar";
                    CHECK_THROWS_AS(j.erase(j.cend(), j.cend()), std::out_of_range);
                    CHECK_THROWS_AS(j.erase(j.cbegin(), j.cbegin()), std::out_of_range);
                    CHECK_THROWS_WITH(j.erase(j.cend(), j.cend()), "iterators out of range");
                    CHECK_THROWS_WITH(j.erase(j.cbegin(), j.cbegin()), "iterators out of range");
                }
            }

            SECTION("number (boolean)")
            {
                {
                    json j = false;
                    CHECK_THROWS_AS(j.erase(j.end(), j.end()), std::out_of_range);
                    CHECK_THROWS_AS(j.erase(j.begin(), j.begin()), std::out_of_range);
                    CHECK_THROWS_WITH(j.erase(j.end(), j.end()), "iterators out of range");
                    CHECK_THROWS_WITH(j.erase(j.begin(), j.begin()), "iterators out of range");
                }
                {
                    json j = true;
                    CHECK_THROWS_AS(j.erase(j.cend(), j.cend()), std::out_of_range);
                    CHECK_THROWS_AS(j.erase(j.cbegin(), j.cbegin()), std::out_of_range);
                    CHECK_THROWS_WITH(j.erase(j.cend(), j.cend()), "iterators out of range");
                    CHECK_THROWS_WITH(j.erase(j.cbegin(), j.cbegin()), "iterators out of range");
                }
            }

            SECTION("number (integer)")
            {
                {
                    json j = 17;
                    CHECK_THROWS_AS(j.erase(j.end(), j.end()), std::out_of_range);
                    CHECK_THROWS_AS(j.erase(j.begin(), j.begin()), std::out_of_range);
                    CHECK_THROWS_WITH(j.erase(j.end(), j.end()), "iterators out of range");
                    CHECK_THROWS_WITH(j.erase(j.begin(), j.begin()), "iterators out of range");
                }
                {
                    json j = 17;
                    CHECK_THROWS_AS(j.erase(j.cend(), j.cend()), std::out_of_range);
                    CHECK_THROWS_AS(j.erase(j.cbegin(), j.cbegin()), std::out_of_range);
                    CHECK_THROWS_WITH(j.erase(j.cend(), j.cend()), "iterators out of range");
                    CHECK_THROWS_WITH(j.erase(j.cbegin(), j.cbegin()), "iterators out of range");
                }
            }

            SECTION("number (unsigned)")
            {
                {
                    json j = 17u;
                    CHECK_THROWS_AS(j.erase(j.end(), j.end()), std::out_of_range);
                    CHECK_THROWS_AS(j.erase(j.begin(), j.begin()), std::out_of_range);
                    CHECK_THROWS_WITH(j.erase(j.end(), j.end()), "iterators out of range");
                    CHECK_THROWS_WITH(j.erase(j.begin(), j.begin()), "iterators out of range");
                }
                {
                    json j = 17u;
                    CHECK_THROWS_AS(j.erase(j.cend(), j.cend()), std::out_of_range);
                    CHECK_THROWS_AS(j.erase(j.cbegin(), j.cbegin()), std::out_of_range);
                    CHECK_THROWS_WITH(j.erase(j.cend(), j.cend()), "iterators out of range");
                    CHECK_THROWS_WITH(j.erase(j.cbegin(), j.cbegin()), "iterators out of range");
                }
            }

            SECTION("number (floating point)")
            {
                {
                    json j = 23.42;
                    CHECK_THROWS_AS(j.erase(j.end(), j.end()), std::out_of_range);
                    CHECK_THROWS_AS(j.erase(j.begin(), j.begin()), std::out_of_range);
                    CHECK_THROWS_WITH(j.erase(j.end(), j.end()), "iterators out of range");
                    CHECK_THROWS_WITH(j.erase(j.begin(), j.begin()), "iterators out of range");
                }
                {
                    json j = 23.42;
                    CHECK_THROWS_AS(j.erase(j.cend(), j.cend()), std::out_of_range);
                    CHECK_THROWS_AS(j.erase(j.cbegin(), j.cbegin()), std::out_of_range);
                    CHECK_THROWS_WITH(j.erase(j.cend(), j.cend()), "iterators out of range");
                    CHECK_THROWS_WITH(j.erase(j.cbegin(), j.cbegin()), "iterators out of range");
                }
            }
        }
    }
}

TEST_CASE("iterators")
{
    SECTION("basic behavior")
    {
        SECTION("uninitialized")
        {
            json::iterator it;
            CHECK(it.m_object == nullptr);

            json::const_iterator cit;
            CHECK(cit.m_object == nullptr);
        }

        SECTION("boolean")
        {
            json j = true;
            json j_const(j);

            SECTION("json + begin/end")
            {
                json::iterator it = j.begin();
                CHECK(it != j.end());
                CHECK(*it == j);

                it++;
                CHECK(it != j.begin());
                CHECK(it == j.end());

                it--;
                CHECK(it == j.begin());
                CHECK(it != j.end());
                CHECK(*it == j);

                ++it;
                CHECK(it != j.begin());
                CHECK(it == j.end());

                --it;
                CHECK(it == j.begin());
                CHECK(it != j.end());
                CHECK(*it == j);
            }

            SECTION("const json + begin/end")
            {
                json::const_iterator it = j_const.begin();
                CHECK(it != j_const.end());
                CHECK(*it == j_const);

                it++;
                CHECK(it != j_const.begin());
                CHECK(it == j_const.end());

                it--;
                CHECK(it == j_const.begin());
                CHECK(it != j_const.end());
                CHECK(*it == j_const);

                ++it;
                CHECK(it != j_const.begin());
                CHECK(it == j_const.end());

                --it;
                CHECK(it == j_const.begin());
                CHECK(it != j_const.end());
                CHECK(*it == j_const);
            }

            SECTION("json + cbegin/cend")
            {
                json::const_iterator it = j.cbegin();
                CHECK(it != j.cend());
                CHECK(*it == j);

                it++;
                CHECK(it != j.cbegin());
                CHECK(it == j.cend());

                it--;
                CHECK(it == j.cbegin());
                CHECK(it != j.cend());
                CHECK(*it == j);

                ++it;
                CHECK(it != j.cbegin());
                CHECK(it == j.cend());

                --it;
                CHECK(it == j.cbegin());
                CHECK(it != j.cend());
                CHECK(*it == j);
            }

            SECTION("const json + cbegin/cend")
            {
                json::const_iterator it = j_const.cbegin();
                CHECK(it != j_const.cend());
                CHECK(*it == j_const);

                it++;
                CHECK(it != j_const.cbegin());
                CHECK(it == j_const.cend());

                it--;
                CHECK(it == j_const.cbegin());
                CHECK(it != j_const.cend());
                CHECK(*it == j_const);

                ++it;
                CHECK(it != j_const.cbegin());
                CHECK(it == j_const.cend());

                --it;
                CHECK(it == j_const.cbegin());
                CHECK(it != j_const.cend());
                CHECK(*it == j_const);
            }

            SECTION("json + rbegin/rend")
            {
                json::reverse_iterator it = j.rbegin();
                CHECK(it != j.rend());
                CHECK(*it == j);

                it++;
                CHECK(it != j.rbegin());
                CHECK(it == j.rend());

                it--;
                CHECK(it == j.rbegin());
                CHECK(it != j.rend());
                CHECK(*it == j);

                ++it;
                CHECK(it != j.rbegin());
                CHECK(it == j.rend());

                --it;
                CHECK(it == j.rbegin());
                CHECK(it != j.rend());
                CHECK(*it == j);
            }

            SECTION("json + crbegin/crend")
            {
                json::const_reverse_iterator it = j.crbegin();
                CHECK(it != j.crend());
                CHECK(*it == j);

                it++;
                CHECK(it != j.crbegin());
                CHECK(it == j.crend());

                it--;
                CHECK(it == j.crbegin());
                CHECK(it != j.crend());
                CHECK(*it == j);

                ++it;
                CHECK(it != j.crbegin());
                CHECK(it == j.crend());

                --it;
                CHECK(it == j.crbegin());
                CHECK(it != j.crend());
                CHECK(*it == j);
            }

            SECTION("const json + crbegin/crend")
            {
                json::const_reverse_iterator it = j_const.crbegin();
                CHECK(it != j_const.crend());
                CHECK(*it == j_const);

                it++;
                CHECK(it != j_const.crbegin());
                CHECK(it == j_const.crend());

                it--;
                CHECK(it == j_const.crbegin());
                CHECK(it != j_const.crend());
                CHECK(*it == j_const);

                ++it;
                CHECK(it != j_const.crbegin());
                CHECK(it == j_const.crend());

                --it;
                CHECK(it == j_const.crbegin());
                CHECK(it != j_const.crend());
                CHECK(*it == j_const);
            }

            SECTION("key/value")
            {
                auto it = j.begin();
                auto cit = j_const.cbegin();
                CHECK_THROWS_AS(it.key(), std::domain_error);
                CHECK_THROWS_WITH(it.key(), "cannot use key() for non-object iterators");
                CHECK(it.value() == json(true));
                CHECK_THROWS_AS(cit.key(), std::domain_error);
                CHECK_THROWS_WITH(cit.key(), "cannot use key() for non-object iterators");
                CHECK(cit.value() == json(true));

                auto rit = j.rend();
                auto crit = j.crend();
                CHECK_THROWS_AS(rit.key(), std::domain_error);
                CHECK_THROWS_AS(rit.value(), std::out_of_range);
                CHECK_THROWS_AS(crit.key(), std::domain_error);
                CHECK_THROWS_AS(crit.value(), std::out_of_range);
                CHECK_THROWS_WITH(rit.key(), "cannot use key() for non-object iterators");
                CHECK_THROWS_WITH(rit.value(), "cannot get value");
                CHECK_THROWS_WITH(crit.key(), "cannot use key() for non-object iterators");
                CHECK_THROWS_WITH(crit.value(), "cannot get value");
            }
        }

        SECTION("string")
        {
            json j = "hello world";
            json j_const(j);

            SECTION("json + begin/end")
            {
                json::iterator it = j.begin();
                CHECK(it != j.end());
                CHECK(*it == j);

                it++;
                CHECK(it != j.begin());
                CHECK(it == j.end());

                it--;
                CHECK(it == j.begin());
                CHECK(it != j.end());
                CHECK(*it == j);

                ++it;
                CHECK(it != j.begin());
                CHECK(it == j.end());

                --it;
                CHECK(it == j.begin());
                CHECK(it != j.end());
                CHECK(*it == j);
            }

            SECTION("const json + begin/end")
            {
                json::const_iterator it = j_const.begin();
                CHECK(it != j_const.end());
                CHECK(*it == j_const);

                it++;
                CHECK(it != j_const.begin());
                CHECK(it == j_const.end());

                it--;
                CHECK(it == j_const.begin());
                CHECK(it != j_const.end());
                CHECK(*it == j_const);

                ++it;
                CHECK(it != j_const.begin());
                CHECK(it == j_const.end());

                --it;
                CHECK(it == j_const.begin());
                CHECK(it != j_const.end());
                CHECK(*it == j_const);
            }

            SECTION("json + cbegin/cend")
            {
                json::const_iterator it = j.cbegin();
                CHECK(it != j.cend());
                CHECK(*it == j);

                it++;
                CHECK(it != j.cbegin());
                CHECK(it == j.cend());

                it--;
                CHECK(it == j.cbegin());
                CHECK(it != j.cend());
                CHECK(*it == j);

                ++it;
                CHECK(it != j.cbegin());
                CHECK(it == j.cend());

                --it;
                CHECK(it == j.cbegin());
                CHECK(it != j.cend());
                CHECK(*it == j);
            }

            SECTION("const json + cbegin/cend")
            {
                json::const_iterator it = j_const.cbegin();
                CHECK(it != j_const.cend());
                CHECK(*it == j_const);

                it++;
                CHECK(it != j_const.cbegin());
                CHECK(it == j_const.cend());

                it--;
                CHECK(it == j_const.cbegin());
                CHECK(it != j_const.cend());
                CHECK(*it == j_const);

                ++it;
                CHECK(it != j_const.cbegin());
                CHECK(it == j_const.cend());

                --it;
                CHECK(it == j_const.cbegin());
                CHECK(it != j_const.cend());
                CHECK(*it == j_const);
            }

            SECTION("json + rbegin/rend")
            {
                json::reverse_iterator it = j.rbegin();
                CHECK(it != j.rend());
                CHECK(*it == j);

                it++;
                CHECK(it != j.rbegin());
                CHECK(it == j.rend());

                it--;
                CHECK(it == j.rbegin());
                CHECK(it != j.rend());
                CHECK(*it == j);

                ++it;
                CHECK(it != j.rbegin());
                CHECK(it == j.rend());

                --it;
                CHECK(it == j.rbegin());
                CHECK(it != j.rend());
                CHECK(*it == j);
            }

            SECTION("json + crbegin/crend")
            {
                json::const_reverse_iterator it = j.crbegin();
                CHECK(it != j.crend());
                CHECK(*it == j);

                it++;
                CHECK(it != j.crbegin());
                CHECK(it == j.crend());

                it--;
                CHECK(it == j.crbegin());
                CHECK(it != j.crend());
                CHECK(*it == j);

                ++it;
                CHECK(it != j.crbegin());
                CHECK(it == j.crend());

                --it;
                CHECK(it == j.crbegin());
                CHECK(it != j.crend());
                CHECK(*it == j);
            }

            SECTION("const json + crbegin/crend")
            {
                json::const_reverse_iterator it = j_const.crbegin();
                CHECK(it != j_const.crend());
                CHECK(*it == j_const);

                it++;
                CHECK(it != j_const.crbegin());
                CHECK(it == j_const.crend());

                it--;
                CHECK(it == j_const.crbegin());
                CHECK(it != j_const.crend());
                CHECK(*it == j_const);

                ++it;
                CHECK(it != j_const.crbegin());
                CHECK(it == j_const.crend());

                --it;
                CHECK(it == j_const.crbegin());
                CHECK(it != j_const.crend());
                CHECK(*it == j_const);
            }

            SECTION("key/value")
            {
                auto it = j.begin();
                auto cit = j_const.cbegin();
                CHECK_THROWS_AS(it.key(), std::domain_error);
                CHECK_THROWS_WITH(it.key(), "cannot use key() for non-object iterators");
                CHECK(it.value() == json("hello world"));
                CHECK_THROWS_AS(cit.key(), std::domain_error);
                CHECK_THROWS_WITH(cit.key(), "cannot use key() for non-object iterators");
                CHECK(cit.value() == json("hello world"));

                auto rit = j.rend();
                auto crit = j.crend();
                CHECK_THROWS_AS(rit.key(), std::domain_error);
                CHECK_THROWS_AS(rit.value(), std::out_of_range);
                CHECK_THROWS_AS(crit.key(), std::domain_error);
                CHECK_THROWS_AS(crit.value(), std::out_of_range);
                CHECK_THROWS_WITH(rit.key(), "cannot use key() for non-object iterators");
                CHECK_THROWS_WITH(rit.value(), "cannot get value");
                CHECK_THROWS_WITH(crit.key(), "cannot use key() for non-object iterators");
                CHECK_THROWS_WITH(crit.value(), "cannot get value");
            }
        }

        SECTION("array")
        {
            json j = {1, 2, 3};
            json j_const(j);

            SECTION("json + begin/end")
            {
                json::iterator it_begin = j.begin();
                json::iterator it_end = j.end();

                auto it = it_begin;
                CHECK(it != it_end);
                CHECK(*it == j[0]);

                it++;
                CHECK(it != it_begin);
                CHECK(it != it_end);
                CHECK(*it == j[1]);

                ++it;
                CHECK(it != it_begin);
                CHECK(it != it_end);
                CHECK(*it == j[2]);

                ++it;
                CHECK(it != it_begin);
                CHECK(it == it_end);
            }

            SECTION("const json + begin/end")
            {
                json::const_iterator it_begin = j_const.begin();
                json::const_iterator it_end = j_const.end();

                auto it = it_begin;
                CHECK(it != it_end);
                CHECK(*it == j_const[0]);

                it++;
                CHECK(it != it_begin);
                CHECK(it != it_end);
                CHECK(*it == j_const[1]);

                ++it;
                CHECK(it != it_begin);
                CHECK(it != it_end);
                CHECK(*it == j_const[2]);

                ++it;
                CHECK(it != it_begin);
                CHECK(it == it_end);
            }

            SECTION("json + cbegin/cend")
            {
                json::const_iterator it_begin = j.cbegin();
                json::const_iterator it_end = j.cend();

                auto it = it_begin;
                CHECK(it != it_end);
                CHECK(*it == j[0]);

                it++;
                CHECK(it != it_begin);
                CHECK(it != it_end);
                CHECK(*it == j[1]);

                ++it;
                CHECK(it != it_begin);
                CHECK(it != it_end);
                CHECK(*it == j[2]);

                ++it;
                CHECK(it != it_begin);
                CHECK(it == it_end);
            }

            SECTION("const json + cbegin/cend")
            {
                json::const_iterator it_begin = j_const.cbegin();
                json::const_iterator it_end = j_const.cend();

                auto it = it_begin;
                CHECK(it != it_end);
                CHECK(*it == j[0]);

                it++;
                CHECK(it != it_begin);
                CHECK(it != it_end);
                CHECK(*it == j[1]);

                ++it;
                CHECK(it != it_begin);
                CHECK(it != it_end);
                CHECK(*it == j[2]);

                ++it;
                CHECK(it != it_begin);
                CHECK(it == it_end);
            }

            SECTION("json + rbegin/rend")
            {
                json::reverse_iterator it_begin = j.rbegin();
                json::reverse_iterator it_end = j.rend();

                auto it = it_begin;
                CHECK(it != it_end);
                CHECK(*it == j[2]);

                it++;
                CHECK(it != it_begin);
                CHECK(it != it_end);
                CHECK(*it == j[1]);

                ++it;
                CHECK(it != it_begin);
                CHECK(it != it_end);
                CHECK(*it == j[0]);

                ++it;
                CHECK(it != it_begin);
                CHECK(it == it_end);
            }

            SECTION("json + crbegin/crend")
            {
                json::const_reverse_iterator it_begin = j.crbegin();
                json::const_reverse_iterator it_end = j.crend();

                auto it = it_begin;
                CHECK(it != it_end);
                CHECK(*it == j[2]);

                it++;
                CHECK(it != it_begin);
                CHECK(it != it_end);
                CHECK(*it == j[1]);

                ++it;
                CHECK(it != it_begin);
                CHECK(it != it_end);
                CHECK(*it == j[0]);

                ++it;
                CHECK(it != it_begin);
                CHECK(it == it_end);
            }

            SECTION("const json + crbegin/crend")
            {
                json::const_reverse_iterator it_begin = j_const.crbegin();
                json::const_reverse_iterator it_end = j_const.crend();

                auto it = it_begin;
                CHECK(it != it_end);
                CHECK(*it == j[2]);

                it++;
                CHECK(it != it_begin);
                CHECK(it != it_end);
                CHECK(*it == j[1]);

                ++it;
                CHECK(it != it_begin);
                CHECK(it != it_end);
                CHECK(*it == j[0]);

                ++it;
                CHECK(it != it_begin);
                CHECK(it == it_end);
            }

            SECTION("key/value")
            {
                auto it = j.begin();
                auto cit = j_const.cbegin();
                CHECK_THROWS_AS(it.key(), std::domain_error);
                CHECK_THROWS_WITH(it.key(), "cannot use key() for non-object iterators");
                CHECK(it.value() == json(1));
                CHECK_THROWS_AS(cit.key(), std::domain_error);
                CHECK_THROWS_WITH(cit.key(), "cannot use key() for non-object iterators");
                CHECK(cit.value() == json(1));
            }
        }

        SECTION("object")
        {
            json j = {{"A", 1}, {"B", 2}, {"C", 3}};
            json j_const(j);

            SECTION("json + begin/end")
            {
                json::iterator it_begin = j.begin();
                json::iterator it_end = j.end();

                auto it = it_begin;
                CHECK(it != it_end);
                CHECK(*it == j["A"]);

                it++;
                CHECK(it != it_begin);
                CHECK(it != it_end);
                CHECK(*it == j["B"]);

                ++it;
                CHECK(it != it_begin);
                CHECK(it != it_end);
                CHECK(*it == j["C"]);

                ++it;
                CHECK(it != it_begin);
                CHECK(it == it_end);
            }

            SECTION("const json + begin/end")
            {
                json::const_iterator it_begin = j_const.begin();
                json::const_iterator it_end = j_const.end();

                auto it = it_begin;
                CHECK(it != it_end);
                CHECK(*it == j_const["A"]);

                it++;
                CHECK(it != it_begin);
                CHECK(it != it_end);
                CHECK(*it == j_const["B"]);

                ++it;
                CHECK(it != it_begin);
                CHECK(it != it_end);
                CHECK(*it == j_const["C"]);

                ++it;
                CHECK(it != it_begin);
                CHECK(it == it_end);
            }

            SECTION("json + cbegin/cend")
            {
                json::const_iterator it_begin = j.cbegin();
                json::const_iterator it_end = j.cend();

                auto it = it_begin;
                CHECK(it != it_end);
                CHECK(*it == j["A"]);

                it++;
                CHECK(it != it_begin);
                CHECK(it != it_end);
                CHECK(*it == j["B"]);

                ++it;
                CHECK(it != it_begin);
                CHECK(it != it_end);
                CHECK(*it == j["C"]);

                ++it;
                CHECK(it != it_begin);
                CHECK(it == it_end);
            }

            SECTION("const json + cbegin/cend")
            {
                json::const_iterator it_begin = j_const.cbegin();
                json::const_iterator it_end = j_const.cend();

                auto it = it_begin;
                CHECK(it != it_end);
                CHECK(*it == j_const["A"]);

                it++;
                CHECK(it != it_begin);
                CHECK(it != it_end);
                CHECK(*it == j_const["B"]);

                ++it;
                CHECK(it != it_begin);
                CHECK(it != it_end);
                CHECK(*it == j_const["C"]);

                ++it;
                CHECK(it != it_begin);
                CHECK(it == it_end);
            }

            SECTION("json + rbegin/rend")
            {
                json::reverse_iterator it_begin = j.rbegin();
                json::reverse_iterator it_end = j.rend();

                auto it = it_begin;
                CHECK(it != it_end);
                CHECK(*it == j["C"]);

                it++;
                CHECK(it != it_begin);
                CHECK(it != it_end);
                CHECK(*it == j["B"]);

                ++it;
                CHECK(it != it_begin);
                CHECK(it != it_end);
                CHECK(*it == j["A"]);

                ++it;
                CHECK(it != it_begin);
                CHECK(it == it_end);
            }

            SECTION("json + crbegin/crend")
            {
                json::const_reverse_iterator it_begin = j.crbegin();
                json::const_reverse_iterator it_end = j.crend();

                auto it = it_begin;
                CHECK(it != it_end);
                CHECK(*it == j["C"]);

                it++;
                CHECK(it != it_begin);
                CHECK(it != it_end);
                CHECK(*it == j["B"]);

                ++it;
                CHECK(it != it_begin);
                CHECK(it != it_end);
                CHECK(*it == j["A"]);

                ++it;
                CHECK(it != it_begin);
                CHECK(it == it_end);
            }

            SECTION("const json + crbegin/crend")
            {
                json::const_reverse_iterator it_begin = j_const.crbegin();
                json::const_reverse_iterator it_end = j_const.crend();

                auto it = it_begin;
                CHECK(it != it_end);
                CHECK(*it == j["C"]);

                it++;
                CHECK(it != it_begin);
                CHECK(it != it_end);
                CHECK(*it == j["B"]);

                ++it;
                CHECK(it != it_begin);
                CHECK(it != it_end);
                CHECK(*it == j["A"]);

                ++it;
                CHECK(it != it_begin);
                CHECK(it == it_end);
            }

            SECTION("key/value")
            {
                auto it = j.begin();
                auto cit = j_const.cbegin();
                CHECK(it.key() == "A");
                CHECK(it.value() == json(1));
                CHECK(cit.key() == "A");
                CHECK(cit.value() == json(1));
            }
        }

        SECTION("number (integer)")
        {
            json j = 23;
            json j_const(j);

            SECTION("json + begin/end")
            {
                json::iterator it = j.begin();
                CHECK(it != j.end());
                CHECK(*it == j);

                it++;
                CHECK(it != j.begin());
                CHECK(it == j.end());

                it--;
                CHECK(it == j.begin());
                CHECK(it != j.end());
                CHECK(*it == j);

                ++it;
                CHECK(it != j.begin());
                CHECK(it == j.end());

                --it;
                CHECK(it == j.begin());
                CHECK(it != j.end());
                CHECK(*it == j);
            }

            SECTION("const json + begin/end")
            {
                json::const_iterator it = j_const.begin();
                CHECK(it != j_const.end());
                CHECK(*it == j_const);

                it++;
                CHECK(it != j_const.begin());
                CHECK(it == j_const.end());

                it--;
                CHECK(it == j_const.begin());
                CHECK(it != j_const.end());
                CHECK(*it == j_const);

                ++it;
                CHECK(it != j_const.begin());
                CHECK(it == j_const.end());

                --it;
                CHECK(it == j_const.begin());
                CHECK(it != j_const.end());
                CHECK(*it == j_const);
            }

            SECTION("json + cbegin/cend")
            {
                json::const_iterator it = j.cbegin();
                CHECK(it != j.cend());
                CHECK(*it == j);

                it++;
                CHECK(it != j.cbegin());
                CHECK(it == j.cend());

                it--;
                CHECK(it == j.cbegin());
                CHECK(it != j.cend());
                CHECK(*it == j);

                ++it;
                CHECK(it != j.cbegin());
                CHECK(it == j.cend());

                --it;
                CHECK(it == j.cbegin());
                CHECK(it != j.cend());
                CHECK(*it == j);
            }

            SECTION("const json + cbegin/cend")
            {
                json::const_iterator it = j_const.cbegin();
                CHECK(it != j_const.cend());
                CHECK(*it == j_const);

                it++;
                CHECK(it != j_const.cbegin());
                CHECK(it == j_const.cend());

                it--;
                CHECK(it == j_const.cbegin());
                CHECK(it != j_const.cend());
                CHECK(*it == j_const);

                ++it;
                CHECK(it != j_const.cbegin());
                CHECK(it == j_const.cend());

                --it;
                CHECK(it == j_const.cbegin());
                CHECK(it != j_const.cend());
                CHECK(*it == j_const);
            }

            SECTION("json + rbegin/rend")
            {
                json::reverse_iterator it = j.rbegin();
                CHECK(it != j.rend());
                CHECK(*it == j);

                it++;
                CHECK(it != j.rbegin());
                CHECK(it == j.rend());

                it--;
                CHECK(it == j.rbegin());
                CHECK(it != j.rend());
                CHECK(*it == j);

                ++it;
                CHECK(it != j.rbegin());
                CHECK(it == j.rend());

                --it;
                CHECK(it == j.rbegin());
                CHECK(it != j.rend());
                CHECK(*it == j);
            }

            SECTION("json + crbegin/crend")
            {
                json::const_reverse_iterator it = j.crbegin();
                CHECK(it != j.crend());
                CHECK(*it == j);

                it++;
                CHECK(it != j.crbegin());
                CHECK(it == j.crend());

                it--;
                CHECK(it == j.crbegin());
                CHECK(it != j.crend());
                CHECK(*it == j);

                ++it;
                CHECK(it != j.crbegin());
                CHECK(it == j.crend());

                --it;
                CHECK(it == j.crbegin());
                CHECK(it != j.crend());
                CHECK(*it == j);
            }

            SECTION("const json + crbegin/crend")
            {
                json::const_reverse_iterator it = j_const.crbegin();
                CHECK(it != j_const.crend());
                CHECK(*it == j_const);

                it++;
                CHECK(it != j_const.crbegin());
                CHECK(it == j_const.crend());

                it--;
                CHECK(it == j_const.crbegin());
                CHECK(it != j_const.crend());
                CHECK(*it == j_const);

                ++it;
                CHECK(it != j_const.crbegin());
                CHECK(it == j_const.crend());

                --it;
                CHECK(it == j_const.crbegin());
                CHECK(it != j_const.crend());
                CHECK(*it == j_const);
            }

            SECTION("key/value")
            {
                auto it = j.begin();
                auto cit = j_const.cbegin();
                CHECK_THROWS_AS(it.key(), std::domain_error);
                CHECK_THROWS_WITH(it.key(), "cannot use key() for non-object iterators");
                CHECK(it.value() == json(23));
                CHECK_THROWS_AS(cit.key(), std::domain_error);
                CHECK_THROWS_WITH(cit.key(), "cannot use key() for non-object iterators");
                CHECK(cit.value() == json(23));

                auto rit = j.rend();
                auto crit = j.crend();
                CHECK_THROWS_AS(rit.key(), std::domain_error);
                CHECK_THROWS_AS(rit.value(), std::out_of_range);
                CHECK_THROWS_AS(crit.key(), std::domain_error);
                CHECK_THROWS_AS(crit.value(), std::out_of_range);
                CHECK_THROWS_WITH(rit.key(), "cannot use key() for non-object iterators");
                CHECK_THROWS_WITH(rit.value(), "cannot get value");
                CHECK_THROWS_WITH(crit.key(), "cannot use key() for non-object iterators");
                CHECK_THROWS_WITH(crit.value(), "cannot get value");
            }
        }

        SECTION("number (unsigned)")
        {
            json j = 23u;
            json j_const(j);

            SECTION("json + begin/end")
            {
                json::iterator it = j.begin();
                CHECK(it != j.end());
                CHECK(*it == j);

                it++;
                CHECK(it != j.begin());
                CHECK(it == j.end());

                it--;
                CHECK(it == j.begin());
                CHECK(it != j.end());
                CHECK(*it == j);

                ++it;
                CHECK(it != j.begin());
                CHECK(it == j.end());

                --it;
                CHECK(it == j.begin());
                CHECK(it != j.end());
                CHECK(*it == j);
            }

            SECTION("const json + begin/end")
            {
                json::const_iterator it = j_const.begin();
                CHECK(it != j_const.end());
                CHECK(*it == j_const);

                it++;
                CHECK(it != j_const.begin());
                CHECK(it == j_const.end());

                it--;
                CHECK(it == j_const.begin());
                CHECK(it != j_const.end());
                CHECK(*it == j_const);

                ++it;
                CHECK(it != j_const.begin());
                CHECK(it == j_const.end());

                --it;
                CHECK(it == j_const.begin());
                CHECK(it != j_const.end());
                CHECK(*it == j_const);
            }

            SECTION("json + cbegin/cend")
            {
                json::const_iterator it = j.cbegin();
                CHECK(it != j.cend());
                CHECK(*it == j);

                it++;
                CHECK(it != j.cbegin());
                CHECK(it == j.cend());

                it--;
                CHECK(it == j.cbegin());
                CHECK(it != j.cend());
                CHECK(*it == j);

                ++it;
                CHECK(it != j.cbegin());
                CHECK(it == j.cend());

                --it;
                CHECK(it == j.cbegin());
                CHECK(it != j.cend());
                CHECK(*it == j);
            }

            SECTION("const json + cbegin/cend")
            {
                json::const_iterator it = j_const.cbegin();
                CHECK(it != j_const.cend());
                CHECK(*it == j_const);

                it++;
                CHECK(it != j_const.cbegin());
                CHECK(it == j_const.cend());

                it--;
                CHECK(it == j_const.cbegin());
                CHECK(it != j_const.cend());
                CHECK(*it == j_const);

                ++it;
                CHECK(it != j_const.cbegin());
                CHECK(it == j_const.cend());

                --it;
                CHECK(it == j_const.cbegin());
                CHECK(it != j_const.cend());
                CHECK(*it == j_const);
            }

            SECTION("json + rbegin/rend")
            {
                json::reverse_iterator it = j.rbegin();
                CHECK(it != j.rend());
                CHECK(*it == j);

                it++;
                CHECK(it != j.rbegin());
                CHECK(it == j.rend());

                it--;
                CHECK(it == j.rbegin());
                CHECK(it != j.rend());
                CHECK(*it == j);

                ++it;
                CHECK(it != j.rbegin());
                CHECK(it == j.rend());

                --it;
                CHECK(it == j.rbegin());
                CHECK(it != j.rend());
                CHECK(*it == j);
            }

            SECTION("json + crbegin/crend")
            {
                json::const_reverse_iterator it = j.crbegin();
                CHECK(it != j.crend());
                CHECK(*it == j);

                it++;
                CHECK(it != j.crbegin());
                CHECK(it == j.crend());

                it--;
                CHECK(it == j.crbegin());
                CHECK(it != j.crend());
                CHECK(*it == j);

                ++it;
                CHECK(it != j.crbegin());
                CHECK(it == j.crend());

                --it;
                CHECK(it == j.crbegin());
                CHECK(it != j.crend());
                CHECK(*it == j);
            }

            SECTION("const json + crbegin/crend")
            {
                json::const_reverse_iterator it = j_const.crbegin();
                CHECK(it != j_const.crend());
                CHECK(*it == j_const);

                it++;
                CHECK(it != j_const.crbegin());
                CHECK(it == j_const.crend());

                it--;
                CHECK(it == j_const.crbegin());
                CHECK(it != j_const.crend());
                CHECK(*it == j_const);

                ++it;
                CHECK(it != j_const.crbegin());
                CHECK(it == j_const.crend());

                --it;
                CHECK(it == j_const.crbegin());
                CHECK(it != j_const.crend());
                CHECK(*it == j_const);
            }

            SECTION("key/value")
            {
                auto it = j.begin();
                auto cit = j_const.cbegin();
                CHECK_THROWS_AS(it.key(), std::domain_error);
                CHECK_THROWS_WITH(it.key(), "cannot use key() for non-object iterators");
                CHECK(it.value() == json(23));
                CHECK_THROWS_AS(cit.key(), std::domain_error);
                CHECK_THROWS_WITH(cit.key(), "cannot use key() for non-object iterators");
                CHECK(cit.value() == json(23));

                auto rit = j.rend();
                auto crit = j.crend();
                CHECK_THROWS_AS(rit.key(), std::domain_error);
                CHECK_THROWS_AS(rit.value(), std::out_of_range);
                CHECK_THROWS_AS(crit.key(), std::domain_error);
                CHECK_THROWS_AS(crit.value(), std::out_of_range);
                CHECK_THROWS_WITH(rit.key(), "cannot use key() for non-object iterators");
                CHECK_THROWS_WITH(rit.value(), "cannot get value");
                CHECK_THROWS_WITH(crit.key(), "cannot use key() for non-object iterators");
                CHECK_THROWS_WITH(crit.value(), "cannot get value");
            }
        }

        SECTION("number (float)")
        {
            json j = 23.42;
            json j_const(j);

            SECTION("json + begin/end")
            {
                json::iterator it = j.begin();
                CHECK(it != j.end());
                CHECK(*it == j);

                it++;
                CHECK(it != j.begin());
                CHECK(it == j.end());

                it--;
                CHECK(it == j.begin());
                CHECK(it != j.end());
                CHECK(*it == j);

                ++it;
                CHECK(it != j.begin());
                CHECK(it == j.end());

                --it;
                CHECK(it == j.begin());
                CHECK(it != j.end());
                CHECK(*it == j);
            }

            SECTION("const json + begin/end")
            {
                json::const_iterator it = j_const.begin();
                CHECK(it != j_const.end());
                CHECK(*it == j_const);

                it++;
                CHECK(it != j_const.begin());
                CHECK(it == j_const.end());

                it--;
                CHECK(it == j_const.begin());
                CHECK(it != j_const.end());
                CHECK(*it == j_const);

                ++it;
                CHECK(it != j_const.begin());
                CHECK(it == j_const.end());

                --it;
                CHECK(it == j_const.begin());
                CHECK(it != j_const.end());
                CHECK(*it == j_const);
            }

            SECTION("json + cbegin/cend")
            {
                json::const_iterator it = j.cbegin();
                CHECK(it != j.cend());
                CHECK(*it == j);

                it++;
                CHECK(it != j.cbegin());
                CHECK(it == j.cend());

                it--;
                CHECK(it == j.cbegin());
                CHECK(it != j.cend());
                CHECK(*it == j);

                ++it;
                CHECK(it != j.cbegin());
                CHECK(it == j.cend());

                --it;
                CHECK(it == j.cbegin());
                CHECK(it != j.cend());
                CHECK(*it == j);
            }

            SECTION("const json + cbegin/cend")
            {
                json::const_iterator it = j_const.cbegin();
                CHECK(it != j_const.cend());
                CHECK(*it == j_const);

                it++;
                CHECK(it != j_const.cbegin());
                CHECK(it == j_const.cend());

                it--;
                CHECK(it == j_const.cbegin());
                CHECK(it != j_const.cend());
                CHECK(*it == j_const);

                ++it;
                CHECK(it != j_const.cbegin());
                CHECK(it == j_const.cend());

                --it;
                CHECK(it == j_const.cbegin());
                CHECK(it != j_const.cend());
                CHECK(*it == j_const);
            }

            SECTION("json + rbegin/rend")
            {
                json::reverse_iterator it = j.rbegin();
                CHECK(it != j.rend());
                CHECK(*it == j);

                it++;
                CHECK(it != j.rbegin());
                CHECK(it == j.rend());

                it--;
                CHECK(it == j.rbegin());
                CHECK(it != j.rend());
                CHECK(*it == j);

                ++it;
                CHECK(it != j.rbegin());
                CHECK(it == j.rend());

                --it;
                CHECK(it == j.rbegin());
                CHECK(it != j.rend());
                CHECK(*it == j);
            }

            SECTION("json + crbegin/crend")
            {
                json::const_reverse_iterator it = j.crbegin();
                CHECK(it != j.crend());
                CHECK(*it == j);

                it++;
                CHECK(it != j.crbegin());
                CHECK(it == j.crend());

                it--;
                CHECK(it == j.crbegin());
                CHECK(it != j.crend());
                CHECK(*it == j);

                ++it;
                CHECK(it != j.crbegin());
                CHECK(it == j.crend());

                --it;
                CHECK(it == j.crbegin());
                CHECK(it != j.crend());
                CHECK(*it == j);
            }

            SECTION("const json + crbegin/crend")
            {
                json::const_reverse_iterator it = j_const.crbegin();
                CHECK(it != j_const.crend());
                CHECK(*it == j_const);

                it++;
                CHECK(it != j_const.crbegin());
                CHECK(it == j_const.crend());

                it--;
                CHECK(it == j_const.crbegin());
                CHECK(it != j_const.crend());
                CHECK(*it == j_const);

                ++it;
                CHECK(it != j_const.crbegin());
                CHECK(it == j_const.crend());

                --it;
                CHECK(it == j_const.crbegin());
                CHECK(it != j_const.crend());
                CHECK(*it == j_const);
            }

            SECTION("key/value")
            {
                auto it = j.begin();
                auto cit = j_const.cbegin();
                CHECK_THROWS_AS(it.key(), std::domain_error);
                CHECK_THROWS_WITH(it.key(), "cannot use key() for non-object iterators");
                CHECK(it.value() == json(23.42));
                CHECK_THROWS_AS(cit.key(), std::domain_error);
                CHECK_THROWS_WITH(cit.key(), "cannot use key() for non-object iterators");
                CHECK(cit.value() == json(23.42));

                auto rit = j.rend();
                auto crit = j.crend();
                CHECK_THROWS_AS(rit.key(), std::domain_error);
                CHECK_THROWS_AS(rit.value(), std::out_of_range);
                CHECK_THROWS_AS(crit.key(), std::domain_error);
                CHECK_THROWS_AS(crit.value(), std::out_of_range);
                CHECK_THROWS_WITH(rit.key(), "cannot use key() for non-object iterators");
                CHECK_THROWS_WITH(rit.value(), "cannot get value");
                CHECK_THROWS_WITH(crit.key(), "cannot use key() for non-object iterators");
                CHECK_THROWS_WITH(crit.value(), "cannot get value");
            }
        }

        SECTION("null")
        {
            json j = nullptr;
            json j_const(j);

            SECTION("json + begin/end")
            {
                json::iterator it = j.begin();
                CHECK(it == j.end());
            }

            SECTION("const json + begin/end")
            {
                json::const_iterator it_begin = j_const.begin();
                json::const_iterator it_end = j_const.end();
                CHECK(it_begin == it_end);
            }

            SECTION("json + cbegin/cend")
            {
                json::const_iterator it_begin = j.cbegin();
                json::const_iterator it_end = j.cend();
                CHECK(it_begin == it_end);
            }

            SECTION("const json + cbegin/cend")
            {
                json::const_iterator it_begin = j_const.cbegin();
                json::const_iterator it_end = j_const.cend();
                CHECK(it_begin == it_end);
            }

            SECTION("json + rbegin/rend")
            {
                json::reverse_iterator it = j.rbegin();
                CHECK(it == j.rend());
            }

            SECTION("json + crbegin/crend")
            {
                json::const_reverse_iterator it = j.crbegin();
                CHECK(it == j.crend());
            }

            SECTION("const json + crbegin/crend")
            {
                json::const_reverse_iterator it = j_const.crbegin();
                CHECK(it == j_const.crend());
            }

            SECTION("key/value")
            {
                auto it = j.begin();
                auto cit = j_const.cbegin();
                CHECK_THROWS_AS(it.key(), std::domain_error);
                CHECK_THROWS_AS(it.value(), std::out_of_range);
                CHECK_THROWS_AS(cit.key(), std::domain_error);
                CHECK_THROWS_AS(cit.value(), std::out_of_range);
                CHECK_THROWS_WITH(it.key(), "cannot use key() for non-object iterators");
                CHECK_THROWS_WITH(it.value(), "cannot get value");
                CHECK_THROWS_WITH(cit.key(), "cannot use key() for non-object iterators");
                CHECK_THROWS_WITH(cit.value(), "cannot get value");

                auto rit = j.rend();
                auto crit = j.crend();
                CHECK_THROWS_AS(rit.key(), std::domain_error);
                CHECK_THROWS_AS(rit.value(), std::out_of_range);
                CHECK_THROWS_AS(crit.key(), std::domain_error);
                CHECK_THROWS_AS(crit.value(), std::out_of_range);
                CHECK_THROWS_WITH(rit.key(), "cannot use key() for non-object iterators");
                CHECK_THROWS_WITH(rit.value(), "cannot get value");
                CHECK_THROWS_WITH(crit.key(), "cannot use key() for non-object iterators");
                CHECK_THROWS_WITH(crit.value(), "cannot get value");
            }
        }
    }

    SECTION("iterator comparisons")
    {
        json j_values = {nullptr, true, 42, 42u, 23.23, {{"one", 1}, {"two", 2}}, {1, 2, 3, 4, 5}, "Hello, world"};

        for (json& j : j_values)
        {
            auto it1 = j.begin();
            auto it2 = j.begin();
            auto it3 = j.begin();
            ++it2;
            ++it3;
            ++it3;
            auto it1_c = j.cbegin();
            auto it2_c = j.cbegin();
            auto it3_c = j.cbegin();
            ++it2_c;
            ++it3_c;
            ++it3_c;

            // comparison: equal
            {
                CHECK(it1 == it1);
                CHECK(not (it1 == it2));
                CHECK(not (it1 == it3));
                CHECK(not (it2 == it3));
                CHECK(it1_c == it1_c);
                CHECK(not (it1_c == it2_c));
                CHECK(not (it1_c == it3_c));
                CHECK(not (it2_c == it3_c));
            }

            // comparison: not equal
            {
                // check definition
                CHECK( (it1 != it1) == not(it1 == it1) );
                CHECK( (it1 != it2) == not(it1 == it2) );
                CHECK( (it1 != it3) == not(it1 == it3) );
                CHECK( (it2 != it3) == not(it2 == it3) );
                CHECK( (it1_c != it1_c) == not(it1_c == it1_c) );
                CHECK( (it1_c != it2_c) == not(it1_c == it2_c) );
                CHECK( (it1_c != it3_c) == not(it1_c == it3_c) );
                CHECK( (it2_c != it3_c) == not(it2_c == it3_c) );
            }

            // comparison: smaller
            {
                if (j.type() == json::value_t::object)
                {
                    CHECK_THROWS_AS(it1 < it1, std::domain_error);
                    CHECK_THROWS_AS(it1 < it2, std::domain_error);
                    CHECK_THROWS_AS(it2 < it3, std::domain_error);
                    CHECK_THROWS_AS(it1 < it3, std::domain_error);
                    CHECK_THROWS_AS(it1_c < it1_c, std::domain_error);
                    CHECK_THROWS_AS(it1_c < it2_c, std::domain_error);
                    CHECK_THROWS_AS(it2_c < it3_c, std::domain_error);
                    CHECK_THROWS_AS(it1_c < it3_c, std::domain_error);
                    CHECK_THROWS_WITH(it1 < it1, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1 < it2, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it2 < it3, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1 < it3, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1_c < it1_c, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1_c < it2_c, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it2_c < it3_c, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1_c < it3_c, "cannot compare order of object iterators");
                }
                else
                {
                    CHECK(not (it1 < it1));
                    CHECK(it1 < it2);
                    CHECK(it1 < it3);
                    CHECK(it2 < it3);
                    CHECK(not (it1_c < it1_c));
                    CHECK(it1_c < it2_c);
                    CHECK(it1_c < it3_c);
                    CHECK(it2_c < it3_c);
                }
            }

            // comparison: less than or equal
            {
                if (j.type() == json::value_t::object)
                {
                    CHECK_THROWS_AS(it1 <= it1, std::domain_error);
                    CHECK_THROWS_AS(it1 <= it2, std::domain_error);
                    CHECK_THROWS_AS(it2 <= it3, std::domain_error);
                    CHECK_THROWS_AS(it1 <= it3, std::domain_error);
                    CHECK_THROWS_AS(it1_c <= it1_c, std::domain_error);
                    CHECK_THROWS_AS(it1_c <= it2_c, std::domain_error);
                    CHECK_THROWS_AS(it2_c <= it3_c, std::domain_error);
                    CHECK_THROWS_AS(it1_c <= it3_c, std::domain_error);
                    CHECK_THROWS_WITH(it1 <= it1, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1 <= it2, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it2 <= it3, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1 <= it3, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1_c <= it1_c, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1_c <= it2_c, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it2_c <= it3_c, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1_c <= it3_c, "cannot compare order of object iterators");
                }
                else
                {
                    // check definition
                    CHECK( (it1 <= it1) == not(it1 < it1) );
                    CHECK( (it1 <= it2) == not(it2 < it1) );
                    CHECK( (it1 <= it3) == not(it3 < it1) );
                    CHECK( (it2 <= it3) == not(it3 < it2) );
                    CHECK( (it1_c <= it1_c) == not(it1_c < it1_c) );
                    CHECK( (it1_c <= it2_c) == not(it2_c < it1_c) );
                    CHECK( (it1_c <= it3_c) == not(it3_c < it1_c) );
                    CHECK( (it2_c <= it3_c) == not(it3_c < it2_c) );
                }
            }

            // comparison: greater than
            {
                if (j.type() == json::value_t::object)
                {
                    CHECK_THROWS_AS(it1 > it1, std::domain_error);
                    CHECK_THROWS_AS(it1 > it2, std::domain_error);
                    CHECK_THROWS_AS(it2 > it3, std::domain_error);
                    CHECK_THROWS_AS(it1 > it3, std::domain_error);
                    CHECK_THROWS_AS(it1_c > it1_c, std::domain_error);
                    CHECK_THROWS_AS(it1_c > it2_c, std::domain_error);
                    CHECK_THROWS_AS(it2_c > it3_c, std::domain_error);
                    CHECK_THROWS_AS(it1_c > it3_c, std::domain_error);
                    CHECK_THROWS_WITH(it1 > it1, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1 > it2, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it2 > it3, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1 > it3, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1_c > it1_c, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1_c > it2_c, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it2_c > it3_c, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1_c > it3_c, "cannot compare order of object iterators");
                }
                else
                {
                    // check definition
                    CHECK( (it1 > it1) == (it1 < it1) );
                    CHECK( (it1 > it2) == (it2 < it1) );
                    CHECK( (it1 > it3) == (it3 < it1) );
                    CHECK( (it2 > it3) == (it3 < it2) );
                    CHECK( (it1_c > it1_c) == (it1_c < it1_c) );
                    CHECK( (it1_c > it2_c) == (it2_c < it1_c) );
                    CHECK( (it1_c > it3_c) == (it3_c < it1_c) );
                    CHECK( (it2_c > it3_c) == (it3_c < it2_c) );
                }
            }

            // comparison: greater than or equal
            {
                if (j.type() == json::value_t::object)
                {
                    CHECK_THROWS_AS(it1 >= it1, std::domain_error);
                    CHECK_THROWS_AS(it1 >= it2, std::domain_error);
                    CHECK_THROWS_AS(it2 >= it3, std::domain_error);
                    CHECK_THROWS_AS(it1 >= it3, std::domain_error);
                    CHECK_THROWS_AS(it1_c >= it1_c, std::domain_error);
                    CHECK_THROWS_AS(it1_c >= it2_c, std::domain_error);
                    CHECK_THROWS_AS(it2_c >= it3_c, std::domain_error);
                    CHECK_THROWS_AS(it1_c >= it3_c, std::domain_error);
                    CHECK_THROWS_WITH(it1 >= it1, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1 >= it2, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it2 >= it3, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1 >= it3, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1_c >= it1_c, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1_c >= it2_c, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it2_c >= it3_c, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1_c >= it3_c, "cannot compare order of object iterators");
                }
                else
                {
                    // check definition
                    CHECK( (it1 >= it1) == not(it1 < it1) );
                    CHECK( (it1 >= it2) == not(it1 < it2) );
                    CHECK( (it1 >= it3) == not(it1 < it3) );
                    CHECK( (it2 >= it3) == not(it2 < it3) );
                    CHECK( (it1_c >= it1_c) == not(it1_c < it1_c) );
                    CHECK( (it1_c >= it2_c) == not(it1_c < it2_c) );
                    CHECK( (it1_c >= it3_c) == not(it1_c < it3_c) );
                    CHECK( (it2_c >= it3_c) == not(it2_c < it3_c) );
                }
            }
        }

        // check exceptions if different objects are compared
        for (auto j : j_values)
        {
            for (auto k : j_values)
            {
                if (j != k)
                {
                    CHECK_THROWS_AS(j.begin() == k.begin(), std::domain_error);
                    CHECK_THROWS_AS(j.cbegin() == k.cbegin(), std::domain_error);
                    CHECK_THROWS_WITH(j.begin() == k.begin(), "cannot compare iterators of different containers");
                    CHECK_THROWS_WITH(j.cbegin() == k.cbegin(), "cannot compare iterators of different containers");

                    CHECK_THROWS_AS(j.begin() < k.begin(), std::domain_error);
                    CHECK_THROWS_AS(j.cbegin() < k.cbegin(), std::domain_error);
                    CHECK_THROWS_WITH(j.begin() < k.begin(), "cannot compare iterators of different containers");
                    CHECK_THROWS_WITH(j.cbegin() < k.cbegin(), "cannot compare iterators of different containers");
                }
            }
        }
    }

    SECTION("iterator arithmetic")
    {
        json j_object = {{"one", 1}, {"two", 2}, {"three", 3}};
        json j_array = {1, 2, 3, 4, 5, 6};
        json j_null = nullptr;
        json j_value = 42;

        SECTION("addition and subtraction")
        {
            SECTION("object")
            {
                {
                    auto it = j_object.begin();
                    CHECK_THROWS_AS(it += 1, std::domain_error);
                    CHECK_THROWS_WITH(it += 1, "cannot use offsets with object iterators");
                }
                {
                    auto it = j_object.cbegin();
                    CHECK_THROWS_AS(it += 1, std::domain_error);
                    CHECK_THROWS_WITH(it += 1, "cannot use offsets with object iterators");
                }
                {
                    auto it = j_object.begin();
                    CHECK_THROWS_AS(it + 1, std::domain_error);
                    CHECK_THROWS_WITH(it + 1, "cannot use offsets with object iterators");
                }
                {
                    auto it = j_object.cbegin();
                    CHECK_THROWS_AS(it + 1, std::domain_error);
                    CHECK_THROWS_WITH(it + 1, "cannot use offsets with object iterators");
                }
                {
                    auto it = j_object.begin();
                    CHECK_THROWS_AS(it -= 1, std::domain_error);
                    CHECK_THROWS_WITH(it -= 1, "cannot use offsets with object iterators");
                }
                {
                    auto it = j_object.cbegin();
                    CHECK_THROWS_AS(it -= 1, std::domain_error);
                    CHECK_THROWS_WITH(it -= 1, "cannot use offsets with object iterators");
                }
                {
                    auto it = j_object.begin();
                    CHECK_THROWS_AS(it - 1, std::domain_error);
                    CHECK_THROWS_WITH(it - 1, "cannot use offsets with object iterators");
                }
                {
                    auto it = j_object.cbegin();
                    CHECK_THROWS_AS(it - 1, std::domain_error);
                    CHECK_THROWS_WITH(it - 1, "cannot use offsets with object iterators");
                }
                {
                    auto it = j_object.begin();
                    CHECK_THROWS_AS(it - it, std::domain_error);
                    CHECK_THROWS_WITH(it - it, "cannot use offsets with object iterators");
                }
                {
                    auto it = j_object.cbegin();
                    CHECK_THROWS_AS(it - it, std::domain_error);
                    CHECK_THROWS_WITH(it - it, "cannot use offsets with object iterators");
                }
            }

            SECTION("array")
            {
                {
                    auto it = j_array.begin();
                    it += 3;
                    CHECK((j_array.begin() + 3) == it);
                    CHECK((it - 3) == j_array.begin());
                    CHECK((it - j_array.begin()) == 3);
                    CHECK(*it == json(4));
                    it -= 2;
                    CHECK(*it == json(2));
                }
                {
                    auto it = j_array.cbegin();
                    it += 3;
                    CHECK((j_array.cbegin() + 3) == it);
                    CHECK((it - 3) == j_array.cbegin());
                    CHECK((it - j_array.cbegin()) == 3);
                    CHECK(*it == json(4));
                    it -= 2;
                    CHECK(*it == json(2));
                }
            }

            SECTION("null")
            {
                {
                    auto it = j_null.begin();
                    it += 3;
                    CHECK((j_null.begin() + 3) == it);
                    CHECK((it - 3) == j_null.begin());
                    CHECK((it - j_null.begin()) == 3);
                    CHECK(it != j_null.end());
                    it -= 3;
                    CHECK(it == j_null.end());
                }
                {
                    auto it = j_null.cbegin();
                    it += 3;
                    CHECK((j_null.cbegin() + 3) == it);
                    CHECK((it - 3) == j_null.cbegin());
                    CHECK((it - j_null.cbegin()) == 3);
                    CHECK(it != j_null.cend());
                    it -= 3;
                    CHECK(it == j_null.cend());
                }
            }

            SECTION("value")
            {
                {
                    auto it = j_value.begin();
                    it += 3;
                    CHECK((j_value.begin() + 3) == it);
                    CHECK((it - 3) == j_value.begin());
                    CHECK((it - j_value.begin()) == 3);
                    CHECK(it != j_value.end());
                    it -= 3;
                    CHECK(*it == json(42));
                }
                {
                    auto it = j_value.cbegin();
                    it += 3;
                    CHECK((j_value.cbegin() + 3) == it);
                    CHECK((it - 3) == j_value.cbegin());
                    CHECK((it - j_value.cbegin()) == 3);
                    CHECK(it != j_value.cend());
                    it -= 3;
                    CHECK(*it == json(42));
                }
            }
        }

        SECTION("subscript operator")
        {
            SECTION("object")
            {
                {
                    auto it = j_object.begin();
                    CHECK_THROWS_AS(it[0], std::domain_error);
                    CHECK_THROWS_AS(it[1], std::domain_error);
                    CHECK_THROWS_WITH(it[0], "cannot use operator[] for object iterators");
                    CHECK_THROWS_WITH(it[1], "cannot use operator[] for object iterators");
                }
                {
                    auto it = j_object.cbegin();
                    CHECK_THROWS_AS(it[0], std::domain_error);
                    CHECK_THROWS_AS(it[1], std::domain_error);
                    CHECK_THROWS_WITH(it[0], "cannot use operator[] for object iterators");
                    CHECK_THROWS_WITH(it[1], "cannot use operator[] for object iterators");
                }
            }

            SECTION("array")
            {
                {
                    auto it = j_array.begin();
                    CHECK(it[0] == json(1));
                    CHECK(it[1] == json(2));
                    CHECK(it[2] == json(3));
                    CHECK(it[3] == json(4));
                    CHECK(it[4] == json(5));
                    CHECK(it[5] == json(6));
                }
                {
                    auto it = j_array.cbegin();
                    CHECK(it[0] == json(1));
                    CHECK(it[1] == json(2));
                    CHECK(it[2] == json(3));
                    CHECK(it[3] == json(4));
                    CHECK(it[4] == json(5));
                    CHECK(it[5] == json(6));
                }
            }

            SECTION("null")
            {
                {
                    auto it = j_null.begin();
                    CHECK_THROWS_AS(it[0], std::out_of_range);
                    CHECK_THROWS_AS(it[1], std::out_of_range);
                    CHECK_THROWS_WITH(it[0], "cannot get value");
                    CHECK_THROWS_WITH(it[1], "cannot get value");
                }
                {
                    auto it = j_null.cbegin();
                    CHECK_THROWS_AS(it[0], std::out_of_range);
                    CHECK_THROWS_AS(it[1], std::out_of_range);
                    CHECK_THROWS_WITH(it[0], "cannot get value");
                    CHECK_THROWS_WITH(it[1], "cannot get value");
                }
            }

            SECTION("value")
            {
                {
                    auto it = j_value.begin();
                    CHECK(it[0] == json(42));
                    CHECK_THROWS_AS(it[1], std::out_of_range);
                    CHECK_THROWS_WITH(it[1], "cannot get value");
                }
                {
                    auto it = j_value.cbegin();
                    CHECK(it[0] == json(42));
                    CHECK_THROWS_AS(it[1], std::out_of_range);
                    CHECK_THROWS_WITH(it[1], "cannot get value");
                }
            }
        }
    }

    SECTION("reverse iterator comparisons")
    {
        json j_values = {nullptr, true, 42, 42u, 23.23, {{"one", 1}, {"two", 2}}, {1, 2, 3, 4, 5}, "Hello, world"};

        for (json& j : j_values)
        {
            auto it1 = j.rbegin();
            auto it2 = j.rbegin();
            auto it3 = j.rbegin();
            ++it2;
            ++it3;
            ++it3;
            auto it1_c = j.crbegin();
            auto it2_c = j.crbegin();
            auto it3_c = j.crbegin();
            ++it2_c;
            ++it3_c;
            ++it3_c;

            // comparison: equal
            {
                CHECK(it1 == it1);
                CHECK(not (it1 == it2));
                CHECK(not (it1 == it3));
                CHECK(not (it2 == it3));
                CHECK(it1_c == it1_c);
                CHECK(not (it1_c == it2_c));
                CHECK(not (it1_c == it3_c));
                CHECK(not (it2_c == it3_c));
            }

            // comparison: not equal
            {
                // check definition
                CHECK( (it1 != it1) == not(it1 == it1) );
                CHECK( (it1 != it2) == not(it1 == it2) );
                CHECK( (it1 != it3) == not(it1 == it3) );
                CHECK( (it2 != it3) == not(it2 == it3) );
                CHECK( (it1_c != it1_c) == not(it1_c == it1_c) );
                CHECK( (it1_c != it2_c) == not(it1_c == it2_c) );
                CHECK( (it1_c != it3_c) == not(it1_c == it3_c) );
                CHECK( (it2_c != it3_c) == not(it2_c == it3_c) );
            }

            // comparison: smaller
            {
                if (j.type() == json::value_t::object)
                {
                    CHECK_THROWS_AS(it1 < it1, std::domain_error);
                    CHECK_THROWS_AS(it1 < it2, std::domain_error);
                    CHECK_THROWS_AS(it2 < it3, std::domain_error);
                    CHECK_THROWS_AS(it1 < it3, std::domain_error);
                    CHECK_THROWS_AS(it1_c < it1_c, std::domain_error);
                    CHECK_THROWS_AS(it1_c < it2_c, std::domain_error);
                    CHECK_THROWS_AS(it2_c < it3_c, std::domain_error);
                    CHECK_THROWS_AS(it1_c < it3_c, std::domain_error);
                    CHECK_THROWS_WITH(it1 < it1, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1 < it2, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it2 < it3, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1 < it3, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1_c < it1_c, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1_c < it2_c, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it2_c < it3_c, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1_c < it3_c, "cannot compare order of object iterators");
                }
                else
                {
                    CHECK(not (it1 < it1));
                    CHECK(it1 < it2);
                    CHECK(it1 < it3);
                    CHECK(it2 < it3);
                    CHECK(not (it1_c < it1_c));
                    CHECK(it1_c < it2_c);
                    CHECK(it1_c < it3_c);
                    CHECK(it2_c < it3_c);
                }
            }

            // comparison: less than or equal
            {
                if (j.type() == json::value_t::object)
                {
                    CHECK_THROWS_AS(it1 <= it1, std::domain_error);
                    CHECK_THROWS_AS(it1 <= it2, std::domain_error);
                    CHECK_THROWS_AS(it2 <= it3, std::domain_error);
                    CHECK_THROWS_AS(it1 <= it3, std::domain_error);
                    CHECK_THROWS_AS(it1_c <= it1_c, std::domain_error);
                    CHECK_THROWS_AS(it1_c <= it2_c, std::domain_error);
                    CHECK_THROWS_AS(it2_c <= it3_c, std::domain_error);
                    CHECK_THROWS_AS(it1_c <= it3_c, std::domain_error);
                    CHECK_THROWS_WITH(it1 <= it1, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1 <= it2, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it2 <= it3, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1 <= it3, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1_c <= it1_c, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1_c <= it2_c, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it2_c <= it3_c, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1_c <= it3_c, "cannot compare order of object iterators");
                }
                else
                {
                    // check definition
                    CHECK( (it1 <= it1) == not(it1 < it1) );
                    CHECK( (it1 <= it2) == not(it2 < it1) );
                    CHECK( (it1 <= it3) == not(it3 < it1) );
                    CHECK( (it2 <= it3) == not(it3 < it2) );
                    CHECK( (it1_c <= it1_c) == not(it1_c < it1_c) );
                    CHECK( (it1_c <= it2_c) == not(it2_c < it1_c) );
                    CHECK( (it1_c <= it3_c) == not(it3_c < it1_c) );
                    CHECK( (it2_c <= it3_c) == not(it3_c < it2_c) );
                }
            }

            // comparison: greater than
            {
                if (j.type() == json::value_t::object)
                {
                    CHECK_THROWS_AS(it1 > it1, std::domain_error);
                    CHECK_THROWS_AS(it1 > it2, std::domain_error);
                    CHECK_THROWS_AS(it2 > it3, std::domain_error);
                    CHECK_THROWS_AS(it1 > it3, std::domain_error);
                    CHECK_THROWS_AS(it1_c > it1_c, std::domain_error);
                    CHECK_THROWS_AS(it1_c > it2_c, std::domain_error);
                    CHECK_THROWS_AS(it2_c > it3_c, std::domain_error);
                    CHECK_THROWS_AS(it1_c > it3_c, std::domain_error);
                    CHECK_THROWS_WITH(it1 > it1, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1 > it2, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it2 > it3, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1 > it3, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1_c > it1_c, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1_c > it2_c, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it2_c > it3_c, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1_c > it3_c, "cannot compare order of object iterators");
                }
                else
                {
                    // check definition
                    CHECK( (it1 > it1) == (it1 < it1) );
                    CHECK( (it1 > it2) == (it2 < it1) );
                    CHECK( (it1 > it3) == (it3 < it1) );
                    CHECK( (it2 > it3) == (it3 < it2) );
                    CHECK( (it1_c > it1_c) == (it1_c < it1_c) );
                    CHECK( (it1_c > it2_c) == (it2_c < it1_c) );
                    CHECK( (it1_c > it3_c) == (it3_c < it1_c) );
                    CHECK( (it2_c > it3_c) == (it3_c < it2_c) );
                }
            }

            // comparison: greater than or equal
            {
                if (j.type() == json::value_t::object)
                {
                    CHECK_THROWS_AS(it1 >= it1, std::domain_error);
                    CHECK_THROWS_AS(it1 >= it2, std::domain_error);
                    CHECK_THROWS_AS(it2 >= it3, std::domain_error);
                    CHECK_THROWS_AS(it1 >= it3, std::domain_error);
                    CHECK_THROWS_AS(it1_c >= it1_c, std::domain_error);
                    CHECK_THROWS_AS(it1_c >= it2_c, std::domain_error);
                    CHECK_THROWS_AS(it2_c >= it3_c, std::domain_error);
                    CHECK_THROWS_AS(it1_c >= it3_c, std::domain_error);
                    CHECK_THROWS_WITH(it1 >= it1, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1 >= it2, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it2 >= it3, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1 >= it3, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1_c >= it1_c, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1_c >= it2_c, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it2_c >= it3_c, "cannot compare order of object iterators");
                    CHECK_THROWS_WITH(it1_c >= it3_c, "cannot compare order of object iterators");
                }
                else
                {
                    // check definition
                    CHECK( (it1 >= it1) == not(it1 < it1) );
                    CHECK( (it1 >= it2) == not(it1 < it2) );
                    CHECK( (it1 >= it3) == not(it1 < it3) );
                    CHECK( (it2 >= it3) == not(it2 < it3) );
                    CHECK( (it1_c >= it1_c) == not(it1_c < it1_c) );
                    CHECK( (it1_c >= it2_c) == not(it1_c < it2_c) );
                    CHECK( (it1_c >= it3_c) == not(it1_c < it3_c) );
                    CHECK( (it2_c >= it3_c) == not(it2_c < it3_c) );
                }
            }
        }

        // check exceptions if different objects are compared
        for (auto j : j_values)
        {
            for (auto k : j_values)
            {
                if (j != k)
                {
                    CHECK_THROWS_AS(j.rbegin() == k.rbegin(), std::domain_error);
                    CHECK_THROWS_AS(j.crbegin() == k.crbegin(), std::domain_error);
                    CHECK_THROWS_WITH(j.rbegin() == k.rbegin(), "cannot compare iterators of different containers");
                    CHECK_THROWS_WITH(j.crbegin() == k.crbegin(), "cannot compare iterators of different containers");

                    CHECK_THROWS_AS(j.rbegin() < k.rbegin(), std::domain_error);
                    CHECK_THROWS_AS(j.crbegin() < k.crbegin(), std::domain_error);
                    CHECK_THROWS_WITH(j.rbegin() < k.rbegin(), "cannot compare iterators of different containers");
                    CHECK_THROWS_WITH(j.crbegin() < k.crbegin(), "cannot compare iterators of different containers");
                }
            }
        }
    }

    SECTION("reverse iterator arithmetic")
    {
        json j_object = {{"one", 1}, {"two", 2}, {"three", 3}};
        json j_array = {1, 2, 3, 4, 5, 6};
        json j_null = nullptr;
        json j_value = 42;

        SECTION("addition and subtraction")
        {
            SECTION("object")
            {
                {
                    auto it = j_object.rbegin();
                    CHECK_THROWS_AS(it += 1, std::domain_error);
                    CHECK_THROWS_WITH(it += 1, "cannot use offsets with object iterators");
                }
                {
                    auto it = j_object.crbegin();
                    CHECK_THROWS_AS(it += 1, std::domain_error);
                    CHECK_THROWS_WITH(it += 1, "cannot use offsets with object iterators");
                }
                {
                    auto it = j_object.rbegin();
                    CHECK_THROWS_AS(it + 1, std::domain_error);
                    CHECK_THROWS_WITH(it + 1, "cannot use offsets with object iterators");
                }
                {
                    auto it = j_object.crbegin();
                    CHECK_THROWS_AS(it + 1, std::domain_error);
                    CHECK_THROWS_WITH(it + 1, "cannot use offsets with object iterators");
                }
                {
                    auto it = j_object.rbegin();
                    CHECK_THROWS_AS(it -= 1, std::domain_error);
                    CHECK_THROWS_WITH(it -= 1, "cannot use offsets with object iterators");
                }
                {
                    auto it = j_object.crbegin();
                    CHECK_THROWS_AS(it -= 1, std::domain_error);
                    CHECK_THROWS_WITH(it -= 1, "cannot use offsets with object iterators");
                }
                {
                    auto it = j_object.rbegin();
                    CHECK_THROWS_AS(it - 1, std::domain_error);
                    CHECK_THROWS_WITH(it - 1, "cannot use offsets with object iterators");
                }
                {
                    auto it = j_object.crbegin();
                    CHECK_THROWS_AS(it - 1, std::domain_error);
                    CHECK_THROWS_WITH(it - 1, "cannot use offsets with object iterators");
                }
                {
                    auto it = j_object.rbegin();
                    CHECK_THROWS_AS(it - it, std::domain_error);
                    CHECK_THROWS_WITH(it - it, "cannot use offsets with object iterators");
                }
                {
                    auto it = j_object.crbegin();
                    CHECK_THROWS_AS(it - it, std::domain_error);
                    CHECK_THROWS_WITH(it - it, "cannot use offsets with object iterators");
                }
            }

            SECTION("array")
            {
                {
                    auto it = j_array.rbegin();
                    it += 3;
                    CHECK((j_array.rbegin() + 3) == it);
                    CHECK((it - 3) == j_array.rbegin());
                    CHECK((j_array.rbegin() - it) == 3);
                    CHECK(*it == json(3));
                    it -= 2;
                    CHECK(*it == json(5));
                }
                {
                    auto it = j_array.crbegin();
                    it += 3;
                    CHECK((j_array.crbegin() + 3) == it);
                    CHECK((it - 3) == j_array.crbegin());
                    CHECK((j_array.crbegin() - it) == 3);
                    CHECK(*it == json(3));
                    it -= 2;
                    CHECK(*it == json(5));
                }
            }

            SECTION("null")
            {
                {
                    auto it = j_null.rbegin();
                    it += 3;
                    CHECK((j_null.rbegin() + 3) == it);
                    CHECK((it - 3) == j_null.rbegin());
                    CHECK((j_null.rbegin() - it) == 3);
                    CHECK(it != j_null.rend());
                    it -= 3;
                    CHECK(it == j_null.rend());
                }
                {
                    auto it = j_null.crbegin();
                    it += 3;
                    CHECK((j_null.crbegin() + 3) == it);
                    CHECK((it - 3) == j_null.crbegin());
                    CHECK((j_null.crbegin() - it) == 3);
                    CHECK(it != j_null.crend());
                    it -= 3;
                    CHECK(it == j_null.crend());
                }
            }

            SECTION("value")
            {
                {
                    auto it = j_value.rbegin();
                    it += 3;
                    CHECK((j_value.rbegin() + 3) == it);
                    CHECK((it - 3) == j_value.rbegin());
                    CHECK((j_value.rbegin() - it) == 3);
                    CHECK(it != j_value.rend());
                    it -= 3;
                    CHECK(*it == json(42));
                }
                {
                    auto it = j_value.crbegin();
                    it += 3;
                    CHECK((j_value.crbegin() + 3) == it);
                    CHECK((it - 3) == j_value.crbegin());
                    CHECK((j_value.crbegin() - it) == 3);
                    CHECK(it != j_value.crend());
                    it -= 3;
                    CHECK(*it == json(42));
                }
            }
        }

        SECTION("subscript operator")
        {
            SECTION("object")
            {
                {
                    auto it = j_object.rbegin();
                    CHECK_THROWS_AS(it[0], std::domain_error);
                    CHECK_THROWS_AS(it[1], std::domain_error);
                    CHECK_THROWS_WITH(it[0], "cannot use offsets with object iterators");
                    CHECK_THROWS_WITH(it[1], "cannot use offsets with object iterators");
                }
                {
                    auto it = j_object.crbegin();
                    CHECK_THROWS_AS(it[0], std::domain_error);
                    CHECK_THROWS_AS(it[1], std::domain_error);
                    CHECK_THROWS_WITH(it[0], "cannot use offsets with object iterators");
                    CHECK_THROWS_WITH(it[1], "cannot use offsets with object iterators");
                }
            }

            SECTION("array")
            {
                {
                    auto it = j_array.rbegin();
                    CHECK(it[0] == json(6));
                    CHECK(it[1] == json(5));
                    CHECK(it[2] == json(4));
                    CHECK(it[3] == json(3));
                    CHECK(it[4] == json(2));
                    CHECK(it[5] == json(1));
                }
                {
                    auto it = j_array.crbegin();
                    CHECK(it[0] == json(6));
                    CHECK(it[1] == json(5));
                    CHECK(it[2] == json(4));
                    CHECK(it[3] == json(3));
                    CHECK(it[4] == json(2));
                    CHECK(it[5] == json(1));
                }
            }

            SECTION("null")
            {
                {
                    auto it = j_null.rbegin();
                    CHECK_THROWS_AS(it[0], std::out_of_range);
                    CHECK_THROWS_AS(it[1], std::out_of_range);
                    CHECK_THROWS_WITH(it[0], "cannot get value");
                    CHECK_THROWS_WITH(it[1], "cannot get value");
                }
                {
                    auto it = j_null.crbegin();
                    CHECK_THROWS_AS(it[0], std::out_of_range);
                    CHECK_THROWS_AS(it[1], std::out_of_range);
                    CHECK_THROWS_WITH(it[0], "cannot get value");
                    CHECK_THROWS_WITH(it[1], "cannot get value");
                }
            }

            SECTION("value")
            {
                {
                    auto it = j_value.rbegin();
                    CHECK(it[0] == json(42));
                    CHECK_THROWS_AS(it[1], std::out_of_range);
                    CHECK_THROWS_WITH(it[1], "cannot get value");
                }
                {
                    auto it = j_value.crbegin();
                    CHECK(it[0] == json(42));
                    CHECK_THROWS_AS(it[1], std::out_of_range);
                    CHECK_THROWS_WITH(it[1], "cannot get value");
                }
            }
        }
    }
}

TEST_CASE("capacity")
{
    SECTION("empty()")
    {
        SECTION("boolean")
        {
            json j = true;
            json j_const(j);

            SECTION("result of empty")
            {
                CHECK(j.empty() == false);
                CHECK(j_const.empty() == false);
            }

            SECTION("definition of empty")
            {
                CHECK(j.begin() != j.end());
                CHECK(j_const.begin() != j_const.end());
            }
        }

        SECTION("string")
        {
            json j = "hello world";
            json j_const(j);

            SECTION("result of empty")
            {
                CHECK(j.empty() == false);
                CHECK(j_const.empty() == false);
            }

            SECTION("definition of empty")
            {
                CHECK(j.begin() != j.end());
                CHECK(j_const.begin() != j_const.end());
            }
        }

        SECTION("array")
        {
            SECTION("empty array")
            {
                json j = json::array();
                json j_const(j);

                SECTION("result of empty")
                {
                    CHECK(j.empty() == true);
                    CHECK(j_const.empty() == true);
                }

                SECTION("definition of empty")
                {
                    CHECK(j.begin() == j.end());
                    CHECK(j_const.begin() == j_const.end());
                }
            }

            SECTION("filled array")
            {
                json j = {1, 2, 3};
                json j_const(j);

                SECTION("result of empty")
                {
                    CHECK(j.empty() == false);
                    CHECK(j_const.empty() == false);
                }

                SECTION("definition of empty")
                {
                    CHECK(j.begin() != j.end());
                    CHECK(j_const.begin() != j_const.end());
                }
            }
        }

        SECTION("object")
        {
            SECTION("empty object")
            {
                json j = json::object();
                json j_const(j);

                SECTION("result of empty")
                {
                    CHECK(j.empty() == true);
                    CHECK(j_const.empty() == true);
                }

                SECTION("definition of empty")
                {
                    CHECK(j.begin() == j.end());
                    CHECK(j_const.begin() == j_const.end());
                }
            }

            SECTION("filled object")
            {
                json j = {{"one", 1}, {"two", 2}, {"three", 3}};
                json j_const(j);

                SECTION("result of empty")
                {
                    CHECK(j.empty() == false);
                    CHECK(j_const.empty() == false);
                }

                SECTION("definition of empty")
                {
                    CHECK(j.begin() != j.end());
                    CHECK(j_const.begin() != j_const.end());
                }
            }
        }

        SECTION("number (integer)")
        {
            json j = 23;
            json j_const(j);

            SECTION("result of empty")
            {
                CHECK(j.empty() == false);
                CHECK(j_const.empty() == false);
            }

            SECTION("definition of empty")
            {
                CHECK(j.begin() != j.end());
                CHECK(j_const.begin() != j_const.end());
            }
        }

        SECTION("number (unsigned)")
        {
            json j = 23u;
            json j_const(j);

            SECTION("result of empty")
            {
                CHECK(j.empty() == false);
                CHECK(j_const.empty() == false);
            }

            SECTION("definition of empty")
            {
                CHECK(j.begin() != j.end());
                CHECK(j_const.begin() != j_const.end());
            }
        }

        SECTION("number (float)")
        {
            json j = 23.42;
            json j_const(j);

            SECTION("result of empty")
            {
                CHECK(j.empty() == false);
                CHECK(j_const.empty() == false);
            }

            SECTION("definition of empty")
            {
                CHECK(j.begin() != j.end());
                CHECK(j_const.begin() != j_const.end());
            }
        }

        SECTION("null")
        {
            json j = nullptr;
            json j_const(j);

            SECTION("result of empty")
            {
                CHECK(j.empty() == true);
                CHECK(j_const.empty() == true);
            }

            SECTION("definition of empty")
            {
                CHECK(j.begin() == j.end());
                CHECK(j_const.begin() == j_const.end());
            }
        }
    }

    SECTION("size()")
    {
        SECTION("boolean")
        {
            json j = true;
            json j_const(j);

            SECTION("result of size")
            {
                CHECK(j.size() == 1);
                CHECK(j_const.size() == 1);
            }

            SECTION("definition of size")
            {
                CHECK(std::distance(j.begin(), j.end()) == j.size());
                CHECK(std::distance(j_const.begin(), j_const.end()) == j_const.size());
                CHECK(std::distance(j.rbegin(), j.rend()) == j.size());
                CHECK(std::distance(j_const.crbegin(), j_const.crend()) == j_const.size());
            }
        }

        SECTION("string")
        {
            json j = "hello world";
            json j_const(j);

            SECTION("result of size")
            {
                CHECK(j.size() == 1);
                CHECK(j_const.size() == 1);
            }

            SECTION("definition of size")
            {
                CHECK(std::distance(j.begin(), j.end()) == j.size());
                CHECK(std::distance(j_const.begin(), j_const.end()) == j_const.size());
                CHECK(std::distance(j.rbegin(), j.rend()) == j.size());
                CHECK(std::distance(j_const.crbegin(), j_const.crend()) == j_const.size());
            }
        }

        SECTION("array")
        {
            SECTION("empty array")
            {
                json j = json::array();
                json j_const(j);

                SECTION("result of size")
                {
                    CHECK(j.size() == 0);
                    CHECK(j_const.size() == 0);
                }

                SECTION("definition of size")
                {
                    CHECK(std::distance(j.begin(), j.end()) == j.size());
                    CHECK(std::distance(j_const.begin(), j_const.end()) == j_const.size());
                    CHECK(std::distance(j.rbegin(), j.rend()) == j.size());
                    CHECK(std::distance(j_const.crbegin(), j_const.crend()) == j_const.size());
                }
            }

            SECTION("filled array")
            {
                json j = {1, 2, 3};
                json j_const(j);

                SECTION("result of size")
                {
                    CHECK(j.size() == 3);
                    CHECK(j_const.size() == 3);
                }

                SECTION("definition of size")
                {
                    CHECK(std::distance(j.begin(), j.end()) == j.size());
                    CHECK(std::distance(j_const.begin(), j_const.end()) == j_const.size());
                    CHECK(std::distance(j.rbegin(), j.rend()) == j.size());
                    CHECK(std::distance(j_const.crbegin(), j_const.crend()) == j_const.size());
                }
            }
        }

        SECTION("object")
        {
            SECTION("empty object")
            {
                json j = json::object();
                json j_const(j);

                SECTION("result of size")
                {
                    CHECK(j.size() == 0);
                    CHECK(j_const.size() == 0);
                }

                SECTION("definition of size")
                {
                    CHECK(std::distance(j.begin(), j.end()) == j.size());
                    CHECK(std::distance(j_const.begin(), j_const.end()) == j_const.size());
                    CHECK(std::distance(j.rbegin(), j.rend()) == j.size());
                    CHECK(std::distance(j_const.crbegin(), j_const.crend()) == j_const.size());
                }
            }

            SECTION("filled object")
            {
                json j = {{"one", 1}, {"two", 2}, {"three", 3}};
                json j_const(j);

                SECTION("result of size")
                {
                    CHECK(j.size() == 3);
                    CHECK(j_const.size() == 3);
                }

                SECTION("definition of size")
                {
                    CHECK(std::distance(j.begin(), j.end()) == j.size());
                    CHECK(std::distance(j_const.begin(), j_const.end()) == j_const.size());
                    CHECK(std::distance(j.rbegin(), j.rend()) == j.size());
                    CHECK(std::distance(j_const.crbegin(), j_const.crend()) == j_const.size());
                }
            }
        }

        SECTION("number (integer)")
        {
            json j = 23;
            json j_const(j);

            SECTION("result of size")
            {
                CHECK(j.size() == 1);
                CHECK(j_const.size() == 1);
            }

            SECTION("definition of size")
            {
                CHECK(std::distance(j.begin(), j.end()) == j.size());
                CHECK(std::distance(j_const.begin(), j_const.end()) == j_const.size());
                CHECK(std::distance(j.rbegin(), j.rend()) == j.size());
                CHECK(std::distance(j_const.crbegin(), j_const.crend()) == j_const.size());
            }
        }

        SECTION("number (unsigned)")
        {
            json j = 23u;
            json j_const(j);

            SECTION("result of size")
            {
                CHECK(j.size() == 1);
                CHECK(j_const.size() == 1);
            }

            SECTION("definition of size")
            {
                CHECK(std::distance(j.begin(), j.end()) == j.size());
                CHECK(std::distance(j_const.begin(), j_const.end()) == j_const.size());
                CHECK(std::distance(j.rbegin(), j.rend()) == j.size());
                CHECK(std::distance(j_const.crbegin(), j_const.crend()) == j_const.size());
            }
        }

        SECTION("number (float)")
        {
            json j = 23.42;
            json j_const(j);

            SECTION("result of size")
            {
                CHECK(j.size() == 1);
                CHECK(j_const.size() == 1);
            }

            SECTION("definition of size")
            {
                CHECK(std::distance(j.begin(), j.end()) == j.size());
                CHECK(std::distance(j_const.begin(), j_const.end()) == j_const.size());
                CHECK(std::distance(j.rbegin(), j.rend()) == j.size());
                CHECK(std::distance(j_const.crbegin(), j_const.crend()) == j_const.size());
            }
        }

        SECTION("null")
        {
            json j = nullptr;
            json j_const(j);

            SECTION("result of size")
            {
                CHECK(j.size() == 0);
                CHECK(j_const.size() == 0);
            }

            SECTION("definition of size")
            {
                CHECK(std::distance(j.begin(), j.end()) == j.size());
                CHECK(std::distance(j_const.begin(), j_const.end()) == j_const.size());
                CHECK(std::distance(j.rbegin(), j.rend()) == j.size());
                CHECK(std::distance(j_const.crbegin(), j_const.crend()) == j_const.size());
            }
        }
    }

    SECTION("max_size()")
    {
        SECTION("boolean")
        {
            json j = true;
            json j_const(j);

            SECTION("result of max_size")
            {
                CHECK(j.max_size() == 1);
                CHECK(j_const.max_size() == 1);
            }
        }

        SECTION("string")
        {
            json j = "hello world";
            json j_const(j);

            SECTION("result of max_size")
            {
                CHECK(j.max_size() == 1);
                CHECK(j_const.max_size() == 1);
            }
        }

        SECTION("array")
        {
            SECTION("empty array")
            {
                json j = json::array();
                json j_const(j);

                SECTION("result of max_size")
                {
                    CHECK(j.max_size() >= j.size());
                    CHECK(j_const.max_size() >= j_const.size());
                }
            }

            SECTION("filled array")
            {
                json j = {1, 2, 3};
                json j_const(j);

                SECTION("result of max_size")
                {
                    CHECK(j.max_size() >= j.size());
                    CHECK(j_const.max_size() >= j_const.size());
                }
            }
        }

        SECTION("object")
        {
            SECTION("empty object")
            {
                json j = json::object();
                json j_const(j);

                SECTION("result of max_size")
                {
                    CHECK(j.max_size() >= j.size());
                    CHECK(j_const.max_size() >= j_const.size());
                }
            }

            SECTION("filled object")
            {
                json j = {{"one", 1}, {"two", 2}, {"three", 3}};
                json j_const(j);

                SECTION("result of max_size")
                {
                    CHECK(j.max_size() >= j.size());
                    CHECK(j_const.max_size() >= j_const.size());
                }
            }
        }

        SECTION("number (integer)")
        {
            json j = 23;
            json j_const(j);

            SECTION("result of max_size")
            {
                CHECK(j.max_size() == 1);
                CHECK(j_const.max_size() == 1);
            }
        }

        SECTION("number (unsigned)")
        {
            json j = 23u;
            json j_const(j);

            SECTION("result of max_size")
            {
                CHECK(j.max_size() == 1);
                CHECK(j_const.max_size() == 1);
            }
        }

        SECTION("number (float)")
        {
            json j = 23.42;
            json j_const(j);

            SECTION("result of max_size")
            {
                CHECK(j.max_size() == 1);
                CHECK(j_const.max_size() == 1);
            }
        }

        SECTION("null")
        {
            json j = nullptr;
            json j_const(j);

            SECTION("result of max_size")
            {
                CHECK(j.max_size() == 0);
                CHECK(j_const.max_size() == 0);
            }
        }
    }
}

TEST_CASE("modifiers")
{
    SECTION("clear()")
    {
        SECTION("boolean")
        {
            json j = true;

            j.clear();
            CHECK(j == json(json::value_t::boolean));
        }

        SECTION("string")
        {
            json j = "hello world";

            j.clear();
            CHECK(j == json(json::value_t::string));
        }

        SECTION("array")
        {
            SECTION("empty array")
            {
                json j = json::array();

                j.clear();
                CHECK(j.empty());
                CHECK(j == json(json::value_t::array));
            }

            SECTION("filled array")
            {
                json j = {1, 2, 3};

                j.clear();
                CHECK(j.empty());
                CHECK(j == json(json::value_t::array));
            }
        }

        SECTION("object")
        {
            SECTION("empty object")
            {
                json j = json::object();

                j.clear();
                CHECK(j.empty());
                CHECK(j == json(json::value_t::object));
            }

            SECTION("filled object")
            {
                json j = {{"one", 1}, {"two", 2}, {"three", 3}};

                j.clear();
                CHECK(j.empty());
                CHECK(j == json(json::value_t::object));
            }
        }

        SECTION("number (integer)")
        {
            json j = 23;

            j.clear();
            CHECK(j == json(json::value_t::number_integer));
        }

        SECTION("number (unsigned)")
        {
            json j = 23u;

            j.clear();
            CHECK(j == json(json::value_t::number_integer));
        }

        SECTION("number (float)")
        {
            json j = 23.42;

            j.clear();
            CHECK(j == json(json::value_t::number_float));
        }

        SECTION("null")
        {
            json j = nullptr;

            j.clear();
            CHECK(j == json(json::value_t::null));
        }
    }

    SECTION("push_back()")
    {
        SECTION("to array")
        {
            SECTION("json&&")
            {
                SECTION("null")
                {
                    json j;
                    j.push_back(1);
                    j.push_back(2);
                    CHECK(j.type() == json::value_t::array);
                    CHECK(j == json({1, 2}));
                }

                SECTION("array")
                {
                    json j = {1, 2, 3};
                    j.push_back("Hello");
                    CHECK(j.type() == json::value_t::array);
                    CHECK(j == json({1, 2, 3, "Hello"}));
                }

                SECTION("other type")
                {
                    json j = 1;
                    CHECK_THROWS_AS(j.push_back("Hello"), std::domain_error);
                    CHECK_THROWS_WITH(j.push_back("Hello"), "cannot use push_back() with number");
                }
            }

            SECTION("const json&")
            {
                SECTION("null")
                {
                    json j;
                    json k(1);
                    j.push_back(k);
                    j.push_back(k);
                    CHECK(j.type() == json::value_t::array);
                    CHECK(j == json({1, 1}));
                }

                SECTION("array")
                {
                    json j = {1, 2, 3};
                    json k("Hello");
                    j.push_back(k);
                    CHECK(j.type() == json::value_t::array);
                    CHECK(j == json({1, 2, 3, "Hello"}));
                }

                SECTION("other type")
                {
                    json j = 1;
                    json k("Hello");
                    CHECK_THROWS_AS(j.push_back(k), std::domain_error);
                    CHECK_THROWS_WITH(j.push_back(k), "cannot use push_back() with number");
                }
            }
        }

        SECTION("to object")
        {
            SECTION("null")
            {
                json j;
                j.push_back(json::object_t::value_type({"one", 1}));
                j.push_back(json::object_t::value_type({"two", 2}));
                CHECK(j.type() == json::value_t::object);
                CHECK(j.size() == 2);
                CHECK(j["one"] == json(1));
                CHECK(j["two"] == json(2));
            }

            SECTION("object")
            {
                json j(json::value_t::object);
                j.push_back(json::object_t::value_type({"one", 1}));
                j.push_back(json::object_t::value_type({"two", 2}));
                CHECK(j.size() == 2);
                CHECK(j["one"] == json(1));
                CHECK(j["two"] == json(2));
            }

            SECTION("other type")
            {
                json j = 1;
                json k("Hello");
                CHECK_THROWS_AS(j.push_back(json::object_t::value_type({"one", 1})), std::domain_error);
                CHECK_THROWS_WITH(j.push_back(json::object_t::value_type({"one", 1})),
                                  "cannot use push_back() with number");
            }
        }

        SECTION("with initializer_list")
        {
            SECTION("null")
            {
                json j;
                j.push_back({"foo", "bar"});
                CHECK(j == json::array({{"foo", "bar"}}));

                json k;
                k.push_back({1, 2, 3});
                CHECK(k == json::array({{1, 2, 3}}));
            }

            SECTION("array")
            {
                json j = {1, 2, 3};
                j.push_back({"foo", "bar"});
                CHECK(j == json({1, 2, 3, {"foo", "bar"}}));

                json k = {1, 2, 3};
                k.push_back({1, 2, 3});
                CHECK(k == json({1, 2, 3, {1, 2, 3}}));
            }

            SECTION("object")
            {
                json j = {{"key1", 1}};
                j.push_back({"key2", "bar"});
                CHECK(j == json({{"key1", 1}, {"key2", "bar"}}));

                json k = {{"key1", 1}};
                CHECK_THROWS_AS(k.push_back({1, 2, 3, 4}), std::domain_error);
                CHECK_THROWS_WITH(k.push_back({1, 2, 3, 4}), "cannot use push_back() with object");
            }
        }
    }

    SECTION("operator+=")
    {
        SECTION("to array")
        {
            SECTION("json&&")
            {
                SECTION("null")
                {
                    json j;
                    j += 1;
                    j += 2;
                    CHECK(j.type() == json::value_t::array);
                    CHECK(j == json({1, 2}));
                }

                SECTION("array")
                {
                    json j = {1, 2, 3};
                    j += "Hello";
                    CHECK(j.type() == json::value_t::array);
                    CHECK(j == json({1, 2, 3, "Hello"}));
                }

                SECTION("other type")
                {
                    json j = 1;
                    CHECK_THROWS_AS(j += "Hello", std::domain_error);
                    CHECK_THROWS_WITH(j += "Hello", "cannot use push_back() with number");
                }
            }

            SECTION("const json&")
            {
                SECTION("null")
                {
                    json j;
                    json k(1);
                    j += k;
                    j += k;
                    CHECK(j.type() == json::value_t::array);
                    CHECK(j == json({1, 1}));
                }

                SECTION("array")
                {
                    json j = {1, 2, 3};
                    json k("Hello");
                    j += k;
                    CHECK(j.type() == json::value_t::array);
                    CHECK(j == json({1, 2, 3, "Hello"}));
                }

                SECTION("other type")
                {
                    json j = 1;
                    json k("Hello");
                    CHECK_THROWS_AS(j += k, std::domain_error);
                    CHECK_THROWS_WITH(j += k, "cannot use push_back() with number");
                }
            }
        }

        SECTION("to object")
        {
            SECTION("null")
            {
                json j;
                j += json::object_t::value_type({"one", 1});
                j += json::object_t::value_type({"two", 2});
                CHECK(j.type() == json::value_t::object);
                CHECK(j.size() == 2);
                CHECK(j["one"] == json(1));
                CHECK(j["two"] == json(2));
            }

            SECTION("object")
            {
                json j(json::value_t::object);
                j += json::object_t::value_type({"one", 1});
                j += json::object_t::value_type({"two", 2});
                CHECK(j.size() == 2);
                CHECK(j["one"] == json(1));
                CHECK(j["two"] == json(2));
            }

            SECTION("other type")
            {
                json j = 1;
                json k("Hello");
                CHECK_THROWS_AS(j += json::object_t::value_type({"one", 1}), std::domain_error);
                CHECK_THROWS_WITH(j += json::object_t::value_type({"one", 1}),
                                  "cannot use push_back() with number");
            }
        }

        SECTION("with initializer_list")
        {
            SECTION("null")
            {
                json j;
                j += {"foo", "bar"};
                CHECK(j == json::array({{"foo", "bar"}}));

                json k;
                k += {1, 2, 3};
                CHECK(k == json::array({{1, 2, 3}}));
            }

            SECTION("array")
            {
                json j = {1, 2, 3};
                j += {"foo", "bar"};
                CHECK(j == json({1, 2, 3, {"foo", "bar"}}));

                json k = {1, 2, 3};
                k += {1, 2, 3};
                CHECK(k == json({1, 2, 3, {1, 2, 3}}));
            }

            SECTION("object")
            {
                json j = {{"key1", 1}};
                j += {"key2", "bar"};
                CHECK(j == json({{"key1", 1}, {"key2", "bar"}}));

                json k = {{"key1", 1}};
                CHECK_THROWS_AS((k += {1, 2, 3, 4}), std::domain_error);
                CHECK_THROWS_WITH((k += {1, 2, 3, 4}), "cannot use push_back() with object");
            }
        }
    }

    SECTION("insert")
    {
        json j_array = {1, 2, 3, 4};
        json j_value = 5;

        SECTION("value at position")
        {
            SECTION("insert before begin()")
            {
                auto it = j_array.insert(j_array.begin(), j_value);
                CHECK(j_array.size() == 5);
                CHECK(*it == j_value);
                CHECK(j_array.begin() == it);
                CHECK(j_array == json({5, 1, 2, 3, 4}));
            }

            SECTION("insert in the middle")
            {
                auto it = j_array.insert(j_array.begin() + 2, j_value);
                CHECK(j_array.size() == 5);
                CHECK(*it == j_value);
                CHECK((it - j_array.begin()) == 2);
                CHECK(j_array == json({1, 2, 5, 3, 4}));
            }

            SECTION("insert before end()")
            {
                auto it = j_array.insert(j_array.end(), j_value);
                CHECK(j_array.size() == 5);
                CHECK(*it == j_value);
                CHECK((j_array.end() - it) == 1);
                CHECK(j_array == json({1, 2, 3, 4, 5}));
            }
        }

        SECTION("rvalue at position")
        {
            SECTION("insert before begin()")
            {
                auto it = j_array.insert(j_array.begin(), 5);
                CHECK(j_array.size() == 5);
                CHECK(*it == j_value);
                CHECK(j_array.begin() == it);
                CHECK(j_array == json({5, 1, 2, 3, 4}));
            }

            SECTION("insert in the middle")
            {
                auto it = j_array.insert(j_array.begin() + 2, 5);
                CHECK(j_array.size() == 5);
                CHECK(*it == j_value);
                CHECK((it - j_array.begin()) == 2);
                CHECK(j_array == json({1, 2, 5, 3, 4}));
            }

            SECTION("insert before end()")
            {
                auto it = j_array.insert(j_array.end(), 5);
                CHECK(j_array.size() == 5);
                CHECK(*it == j_value);
                CHECK((j_array.end() - it) == 1);
                CHECK(j_array == json({1, 2, 3, 4, 5}));
            }
        }

        SECTION("copies at position")
        {
            SECTION("insert before begin()")
            {
                auto it = j_array.insert(j_array.begin(), 3, 5);
                CHECK(j_array.size() == 7);
                CHECK(*it == j_value);
                CHECK(j_array.begin() == it);
                CHECK(j_array == json({5, 5, 5, 1, 2, 3, 4}));
            }

            SECTION("insert in the middle")
            {
                auto it = j_array.insert(j_array.begin() + 2, 3, 5);
                CHECK(j_array.size() == 7);
                CHECK(*it == j_value);
                CHECK((it - j_array.begin()) == 2);
                CHECK(j_array == json({1, 2, 5, 5, 5, 3, 4}));
            }

            SECTION("insert before end()")
            {
                auto it = j_array.insert(j_array.end(), 3, 5);
                CHECK(j_array.size() == 7);
                CHECK(*it == j_value);
                CHECK((j_array.end() - it) == 3);
                CHECK(j_array == json({1, 2, 3, 4, 5, 5, 5}));
            }

            SECTION("insert nothing (count = 0)")
            {
                auto pos = j_array.end();
                auto it = j_array.insert(j_array.end(), 0, 5);
                CHECK(j_array.size() == 4);
                CHECK(it == pos);
                CHECK(j_array == json({1, 2, 3, 4}));
            }
        }

        SECTION("range")
        {
            json j_other_array = {"first", "second"};

            SECTION("proper usage")
            {
                auto it = j_array.insert(j_array.end(), j_other_array.begin(), j_other_array.end());
                CHECK(j_array.size() == 6);
                CHECK(*it == *j_other_array.begin());
                CHECK((j_array.end() - it) == 2);
                CHECK(j_array == json({1, 2, 3, 4, "first", "second"}));
            }

            SECTION("empty range")
            {
                auto it = j_array.insert(j_array.end(), j_other_array.begin(), j_other_array.begin());
                CHECK(j_array.size() == 4);
                CHECK(it == j_array.end());
                CHECK(j_array == json({1, 2, 3, 4}));
            }

            SECTION("invalid iterators")
            {
                json j_other_array2 = {"first", "second"};

                CHECK_THROWS_AS(j_array.insert(j_array.end(), j_array.begin(), j_array.end()), std::domain_error);
                CHECK_THROWS_AS(j_array.insert(j_array.end(), j_other_array.begin(), j_other_array2.end()),
                                std::domain_error);

                CHECK_THROWS_WITH(j_array.insert(j_array.end(), j_array.begin(), j_array.end()),
                                  "passed iterators may not belong to container");
                CHECK_THROWS_WITH(j_array.insert(j_array.end(), j_other_array.begin(), j_other_array2.end()),
                                  "iterators do not fit");
            }
        }

        SECTION("initializer list at position")
        {
            SECTION("insert before begin()")
            {
                auto it = j_array.insert(j_array.begin(), {7, 8, 9});
                CHECK(j_array.size() == 7);
                CHECK(*it == json(7));
                CHECK(j_array.begin() == it);
                CHECK(j_array == json({7, 8, 9, 1, 2, 3, 4}));
            }

            SECTION("insert in the middle")
            {
                auto it = j_array.insert(j_array.begin() + 2, {7, 8, 9});
                CHECK(j_array.size() == 7);
                CHECK(*it == json(7));
                CHECK((it - j_array.begin()) == 2);
                CHECK(j_array == json({1, 2, 7, 8, 9, 3, 4}));
            }

            SECTION("insert before end()")
            {
                auto it = j_array.insert(j_array.end(), {7, 8, 9});
                CHECK(j_array.size() == 7);
                CHECK(*it == json(7));
                CHECK((j_array.end() - it) == 3);
                CHECK(j_array == json({1, 2, 3, 4, 7, 8, 9}));
            }
        }

        SECTION("invalid iterator")
        {
            // pass iterator to a different array
            json j_another_array = {1, 2};
            json j_yet_another_array = {"first", "second"};
            CHECK_THROWS_AS(j_array.insert(j_another_array.end(), 10), std::domain_error);
            CHECK_THROWS_AS(j_array.insert(j_another_array.end(), j_value), std::domain_error);
            CHECK_THROWS_AS(j_array.insert(j_another_array.end(), 10, 11), std::domain_error);
            CHECK_THROWS_AS(j_array.insert(j_another_array.end(), j_yet_another_array.begin(),
                                           j_yet_another_array.end()), std::domain_error);
            CHECK_THROWS_AS(j_array.insert(j_another_array.end(), {1, 2, 3, 4}), std::domain_error);

            CHECK_THROWS_WITH(j_array.insert(j_another_array.end(), 10), "iterator does not fit current value");
            CHECK_THROWS_WITH(j_array.insert(j_another_array.end(), j_value),
                              "iterator does not fit current value");
            CHECK_THROWS_WITH(j_array.insert(j_another_array.end(), 10, 11),
                              "iterator does not fit current value");
            CHECK_THROWS_WITH(j_array.insert(j_another_array.end(), j_yet_another_array.begin(),
                                             j_yet_another_array.end()), "iterator does not fit current value");
            CHECK_THROWS_WITH(j_array.insert(j_another_array.end(), {1, 2, 3, 4}),
                              "iterator does not fit current value");
        }

        SECTION("non-array type")
        {
            // call insert on a non-array type
            json j_nonarray = 3;
            json j_yet_another_array = {"first", "second"};
            CHECK_THROWS_AS(j_nonarray.insert(j_nonarray.end(), 10), std::domain_error);
            CHECK_THROWS_AS(j_nonarray.insert(j_nonarray.end(), j_value), std::domain_error);
            CHECK_THROWS_AS(j_nonarray.insert(j_nonarray.end(), 10, 11), std::domain_error);
            CHECK_THROWS_AS(j_nonarray.insert(j_nonarray.end(), j_yet_another_array.begin(),
                                              j_yet_another_array.end()), std::domain_error);
            CHECK_THROWS_AS(j_nonarray.insert(j_nonarray.end(), {1, 2, 3, 4}), std::domain_error);

            CHECK_THROWS_WITH(j_nonarray.insert(j_nonarray.end(), 10), "cannot use insert() with number");
            CHECK_THROWS_WITH(j_nonarray.insert(j_nonarray.end(), j_value), "cannot use insert() with number");
            CHECK_THROWS_WITH(j_nonarray.insert(j_nonarray.end(), 10, 11), "cannot use insert() with number");
            CHECK_THROWS_WITH(j_nonarray.insert(j_nonarray.end(), j_yet_another_array.begin(),
                                                j_yet_another_array.end()), "cannot use insert() with number");
            CHECK_THROWS_WITH(j_nonarray.insert(j_nonarray.end(), {1, 2, 3, 4}),
                              "cannot use insert() with number");
        }
    }

    SECTION("swap()")
    {
        SECTION("json")
        {
            SECTION("member swap")
            {
                json j("hello world");
                json k(42.23);

                j.swap(k);

                CHECK(j == json(42.23));
                CHECK(k == json("hello world"));
            }

            SECTION("nonmember swap")
            {
                json j("hello world");
                json k(42.23);

                std::swap(j, k);

                CHECK(j == json(42.23));
                CHECK(k == json("hello world"));
            }
        }

        SECTION("array_t")
        {
            SECTION("array_t type")
            {
                json j = {1, 2, 3, 4};
                json::array_t a = {"foo", "bar", "baz"};

                j.swap(a);

                CHECK(j == json({"foo", "bar", "baz"}));

                j.swap(a);

                CHECK(j == json({1, 2, 3, 4}));
            }

            SECTION("non-array_t type")
            {
                json j = 17;
                json::array_t a = {"foo", "bar", "baz"};

                CHECK_THROWS_AS(j.swap(a), std::domain_error);
                CHECK_THROWS_WITH(j.swap(a), "cannot use swap() with number");
            }
        }

        SECTION("object_t")
        {
            SECTION("object_t type")
            {
                json j = {{"one", 1}, {"two", 2}};
                json::object_t o = {{"cow", "Kuh"}, {"chicken", "Huhn"}};

                j.swap(o);

                CHECK(j == json({{"cow", "Kuh"}, {"chicken", "Huhn"}}));

                j.swap(o);

                CHECK(j == json({{"one", 1}, {"two", 2}}));
            }

            SECTION("non-object_t type")
            {
                json j = 17;
                json::object_t o = {{"cow", "Kuh"}, {"chicken", "Huhn"}};

                CHECK_THROWS_AS(j.swap(o), std::domain_error);
                CHECK_THROWS_WITH(j.swap(o), "cannot use swap() with number");
            }
        }

        SECTION("string_t")
        {
            SECTION("string_t type")
            {
                json j = "Hello world";
                json::string_t s = "Hallo Welt";

                j.swap(s);

                CHECK(j == json("Hallo Welt"));

                j.swap(s);

                CHECK(j == json("Hello world"));
            }

            SECTION("non-string_t type")
            {
                json j = 17;
                json::string_t s = "Hallo Welt";

                CHECK_THROWS_AS(j.swap(s), std::domain_error);
                CHECK_THROWS_WITH(j.swap(s), "cannot use swap() with number");
            }
        }
    }
}

TEST_CASE("lexicographical comparison operators")
{
    SECTION("types")
    {
        std::vector<json::value_t> j_types =
        {
            json::value_t::null,
            json::value_t::boolean,
            json::value_t::number_integer,
            json::value_t::number_unsigned,
            json::value_t::number_float,
            json::value_t::object,
            json::value_t::array,
            json::value_t::string
        };

        SECTION("comparison: less")
        {
            std::vector<std::vector<bool>> expected =
            {
                {false, true, true, true, true, true, true, true},
                {false, false, true, true, true, true, true, true},
                {false, false, false, false, false, true, true, true},
                {false, false, false, false, false, true, true, true},
                {false, false, false, false, false, true, true, true},
                {false, false, false, false, false, false, true, true},
                {false, false, false, false, false, false, false, true},
                {false, false, false, false, false, false, false, false}
            };

            for (size_t i = 0; i < j_types.size(); ++i)
            {
                for (size_t j = 0; j < j_types.size(); ++j)
                {
                    CAPTURE(i);
                    CAPTURE(j);
                    // check precomputed values
                    CHECK(operator<(j_types[i], j_types[j]) == expected[i][j]);
                }
            }
        }
    }

    SECTION("values")
    {
        json j_values =
        {
            nullptr, nullptr,
            17, 42,
            8u, 13u,
            3.14159, 23.42,
            "foo", "bar",
            true, false,
            {1, 2, 3}, {"one", "two", "three"},
            {{"first", 1}, {"second", 2}}, {{"a", "A"}, {"b", {"B"}}}
        };

        SECTION("comparison: equal")
        {
            std::vector<std::vector<bool>> expected =
            {
                {true, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false},
                {true, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false},
                {false, false, true, false, false, false, false, false, false, false, false, false, false, false, false, false},
                {false, false, false, true, false, false, false, false, false, false, false, false, false, false, false, false},
                {false, false, false, false, true, false, false, false, false, false, false, false, false, false, false, false},
                {false, false, false, false, false, true, false, false, false, false, false, false, false, false, false, false},
                {false, false, false, false, false, false, true, false, false, false, false, false, false, false, false, false},
                {false, false, false, false, false, false, false, true, false, false, false, false, false, false, false, false},
                {false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, false},
                {false, false, false, false, false, false, false, false, false, true, false, false, false, false, false, false},
                {false, false, false, false, false, false, false, false, false, false, true, false, false, false, false, false},
                {false, false, false, false, false, false, false, false, false, false, false, true, false, false, false, false},
                {false, false, false, false, false, false, false, false, false, false, false, false, true, false, false, false},
                {false, false, false, false, false, false, false, false, false, false, false, false, false, true, false, false},
                {false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, false},
                {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true}
            };

            for (size_t i = 0; i < j_values.size(); ++i)
            {
                for (size_t j = 0; j < j_values.size(); ++j)
                {
                    CAPTURE(i);
                    CAPTURE(j);
                    // check precomputed values
                    CHECK( (j_values[i] == j_values[j]) == expected[i][j] );
                }
            }

            // comparison with discarded elements
            json j_discarded(json::value_t::discarded);
            for (size_t i = 0; i < j_values.size(); ++i)
            {
                CHECK( (j_values[i] == j_discarded) == false);
                CHECK( (j_discarded == j_values[i]) == false);
                CHECK( (j_discarded == j_discarded) == false);
            }

            // compare with null pointer
            json j_null;
            CHECK(j_null == nullptr);
            CHECK(nullptr == j_null);
        }

        SECTION("comparison: not equal")
        {
            for (size_t i = 0; i < j_values.size(); ++i)
            {
                for (size_t j = 0; j < j_values.size(); ++j)
                {
                    CAPTURE(i);
                    CAPTURE(j);
                    // check definition
                    CHECK( (j_values[i] != j_values[j]) == not(j_values[i] == j_values[j]) );
                }
            }

            // compare with null pointer
            json j_null;
            CHECK( (j_null != nullptr) == false);
            CHECK( (nullptr != j_null) == false);
            CHECK( (j_null != nullptr) == not(j_null == nullptr));
            CHECK( (nullptr != j_null) == not(nullptr == j_null));
        }

        SECTION("comparison: less")
        {
            std::vector<std::vector<bool>> expected =
            {
                {false, false, true, true, true, true, true, true, true, true, true, true, true, true, true, true},
                {false, false, true, true, true, true, true, true, true, true, true, true, true, true, true, true},
                {false, false, false, true, false, false, false, true, true, true, false, false, true, true, true, true},
                {false, false, false, false, false, false, false, false, true, true, false, false, true, true, true, true},
                {false, false, true, true, false, true, false, true, true, true, false, false, true, true, true, true},
                {false, false, true, true, false, false, false, true, true, true, false, false, true, true, true, true},
                {false, false, true, true, true, true, false, true, true, true, false, false, true, true, true, true},
                {false, false, false, true, false, false, false, false, true, true, false, false, true, true, true, true},
                {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false},
                {false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, false},
                {false, false, true, true, true, true, true, true, true, true, false, false, true, true, true, true},
                {false, false, true, true, true, true, true, true, true, true, true, false, true, true, true, true},
                {false, false, false, false, false, false, false, false, true, true, false, false, false, true, false, false},
                {false, false, false, false, false, false, false, false, true, true, false, false, false, false, false, false},
                {false, false, false, false, false, false, false, false, true, true, false, false, true, true, false, false},
                {false, false, false, false, false, false, false, false, true, true, false, false, true, true, true, false}
            };

            for (size_t i = 0; i < j_values.size(); ++i)
            {
                for (size_t j = 0; j < j_values.size(); ++j)
                {
                    CAPTURE(i);
                    CAPTURE(j);
                    // check precomputed values
                    CHECK( (j_values[i] < j_values[j]) == expected[i][j] );
                }
            }

            // comparison with discarded elements
            json j_discarded(json::value_t::discarded);
            for (size_t i = 0; i < j_values.size(); ++i)
            {
                CAPTURE(i);
                CHECK( (j_values[i] < j_discarded) == false);
                CHECK( (j_discarded < j_values[i]) == false);
                CHECK( (j_discarded < j_discarded) == false);
            }
        }

        SECTION("comparison: less than or equal equal")
        {
            for (size_t i = 0; i < j_values.size(); ++i)
            {
                for (size_t j = 0; j < j_values.size(); ++j)
                {
                    CAPTURE(i);
                    CAPTURE(j);
                    // check definition
                    CHECK( (j_values[i] <= j_values[j]) == not(j_values[j] < j_values[i]) );
                }
            }
        }

        SECTION("comparison: greater than")
        {
            for (size_t i = 0; i < j_values.size(); ++i)
            {
                for (size_t j = 0; j < j_values.size(); ++j)
                {
                    CAPTURE(i);
                    CAPTURE(j);
                    // check definition
                    CHECK( (j_values[i] > j_values[j]) == (j_values[j] < j_values[i]) );
                }
            }
        }

        SECTION("comparison: greater than or equal")
        {
            for (size_t i = 0; i < j_values.size(); ++i)
            {
                for (size_t j = 0; j < j_values.size(); ++j)
                {
                    CAPTURE(i);
                    CAPTURE(j);
                    // check definition
                    CHECK( (j_values[i] >= j_values[j]) == not(j_values[i] < j_values[j]) );
                }
            }
        }
    }
}

TEST_CASE("serialization")
{
    SECTION("operator<<")
    {
        SECTION("no given width")
        {
            std::stringstream ss;
            json j = {"foo", 1, 2, 3, false, {{"one", 1}}};
            ss << j;
            CHECK(ss.str() == "[\"foo\",1,2,3,false,{\"one\":1}]");
        }

        SECTION("given width")
        {
            std::stringstream ss;
            json j = {"foo", 1, 2, 3, false, {{"one", 1}}};
            ss << std::setw(4) << j;
            CHECK(ss.str() ==
                  "[\n    \"foo\",\n    1,\n    2,\n    3,\n    false,\n    {\n        \"one\": 1\n    }\n]");
        }
    }

    SECTION("operator>>")
    {
        SECTION("no given width")
        {
            std::stringstream ss;
            json j = {"foo", 1, 2, 3, false, {{"one", 1}}};
            j >> ss;
            CHECK(ss.str() == "[\"foo\",1,2,3,false,{\"one\":1}]");
        }

        SECTION("given width")
        {
            std::stringstream ss;
            json j = {"foo", 1, 2, 3, false, {{"one", 1}}};
            ss.width(4);
            j >> ss;
            CHECK(ss.str() ==
                  "[\n    \"foo\",\n    1,\n    2,\n    3,\n    false,\n    {\n        \"one\": 1\n    }\n]");
        }
    }
}

TEST_CASE("deserialization")
{
    SECTION("stream")
    {
        std::stringstream ss;
        ss << "[\"foo\",1,2,3,false,{\"one\":1}]";
        json j = json::parse(ss);
        CHECK(j == json({"foo", 1, 2, 3, false, {{"one", 1}}}));
    }

    SECTION("string")
    {
        auto s = "[\"foo\",1,2,3,false,{\"one\":1}]";
        json j = json::parse(s);
        CHECK(j == json({"foo", 1, 2, 3, false, {{"one", 1}}}));
    }

    SECTION("operator<<")
    {
        std::stringstream ss;
        ss << "[\"foo\",1,2,3,false,{\"one\":1}]";
        json j;
        j << ss;
        CHECK(j == json({"foo", 1, 2, 3, false, {{"one", 1}}}));
    }

    SECTION("operator>>")
    {
        std::stringstream ss;
        ss << "[\"foo\",1,2,3,false,{\"one\":1}]";
        json j;
        ss >> j;
        CHECK(j == json({"foo", 1, 2, 3, false, {{"one", 1}}}));
    }

    SECTION("user-defined string literal")
    {
        CHECK("[\"foo\",1,2,3,false,{\"one\":1}]"_json == json({"foo", 1, 2, 3, false, {{"one", 1}}}));
    }
}

TEST_CASE("iterator class")
{
    SECTION("construction")
    {
        SECTION("constructor")
        {
            SECTION("null")
            {
                json j(json::value_t::null);
                json::iterator it(&j);
            }

            SECTION("object")
            {
                json j(json::value_t::object);
                json::iterator it(&j);
            }

            SECTION("array")
            {
                json j(json::value_t::array);
                json::iterator it(&j);
            }
        }

        SECTION("copy assignment")
        {
            json j(json::value_t::null);
            json::iterator it(&j);
            json::iterator it2(&j);
            it2 = it;
        }
    }

    SECTION("initialization")
    {
        SECTION("set_begin")
        {
            SECTION("null")
            {
                json j(json::value_t::null);
                json::iterator it(&j);
                it.set_begin();
                CHECK(it == j.begin());
            }

            SECTION("object")
            {
                json j(json::value_t::object);
                json::iterator it(&j);
                it.set_begin();
                CHECK(it == j.begin());
            }

            SECTION("array")
            {
                json j(json::value_t::array);
                json::iterator it(&j);
                it.set_begin();
                CHECK(it == j.begin());
            }
        }

        SECTION("set_end")
        {
            SECTION("null")
            {
                json j(json::value_t::null);
                json::iterator it(&j);
                it.set_end();
                CHECK(it == j.end());
            }

            SECTION("object")
            {
                json j(json::value_t::object);
                json::iterator it(&j);
                it.set_end();
                CHECK(it == j.end());
            }

            SECTION("array")
            {
                json j(json::value_t::array);
                json::iterator it(&j);
                it.set_end();
                CHECK(it == j.end());
            }
        }
    }

    SECTION("element access")
    {
        SECTION("operator*")
        {
            SECTION("null")
            {
                json j(json::value_t::null);
                json::iterator it = j.begin();
                CHECK_THROWS_AS(*it, std::out_of_range);
                CHECK_THROWS_WITH(*it, "cannot get value");
            }

            SECTION("number")
            {
                json j(17);
                json::iterator it = j.begin();
                CHECK(*it == json(17));
                it = j.end();
                CHECK_THROWS_AS(*it, std::out_of_range);
                CHECK_THROWS_WITH(*it, "cannot get value");
            }

            SECTION("object")
            {
                json j({{"foo", "bar"}});
                json::iterator it = j.begin();
                CHECK(*it == json("bar"));
            }

            SECTION("array")
            {
                json j({1, 2, 3, 4});
                json::iterator it = j.begin();
                CHECK(*it == json(1));
            }
        }

        SECTION("operator->")
        {
            SECTION("null")
            {
                json j(json::value_t::null);
                json::iterator it = j.begin();
                CHECK_THROWS_AS(it->type_name(), std::out_of_range);
                CHECK_THROWS_WITH(it->type_name(), "cannot get value");
            }

            SECTION("number")
            {
                json j(17);
                json::iterator it = j.begin();
                CHECK(it->type_name() == "number");
                it = j.end();
                CHECK_THROWS_AS(it->type_name(), std::out_of_range);
                CHECK_THROWS_WITH(it->type_name(), "cannot get value");
            }

            SECTION("object")
            {
                json j({{"foo", "bar"}});
                json::iterator it = j.begin();
                CHECK(it->type_name() == "string");
            }

            SECTION("array")
            {
                json j({1, 2, 3, 4});
                json::iterator it = j.begin();
                CHECK(it->type_name() == "number");
            }
        }
    }

    SECTION("increment/decrement")
    {
        SECTION("post-increment")
        {
            SECTION("null")
            {
                json j(json::value_t::null);
                json::iterator it = j.begin();
                CHECK(it.m_it.primitive_iterator == 1);
                it++;
                CHECK((it.m_it.primitive_iterator != 0 and it.m_it.primitive_iterator != 1));
            }

            SECTION("number")
            {
                json j(17);
                json::iterator it = j.begin();
                CHECK(it.m_it.primitive_iterator == 0);
                it++;
                CHECK(it.m_it.primitive_iterator == 1);
                it++;
                CHECK((it.m_it.primitive_iterator != 0 and it.m_it.primitive_iterator != 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());
                it++;
                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());
                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());
                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());
                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());
                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());
            }
        }

        SECTION("pre-increment")
        {
            SECTION("null")
            {
                json j(json::value_t::null);
                json::iterator it = j.begin();
                CHECK(it.m_it.primitive_iterator == 1);
                ++it;
                CHECK((it.m_it.primitive_iterator != 0 and it.m_it.primitive_iterator != 1));
            }

            SECTION("number")
            {
                json j(17);
                json::iterator it = j.begin();
                CHECK(it.m_it.primitive_iterator == 0);
                ++it;
                CHECK(it.m_it.primitive_iterator == 1);
                ++it;
                CHECK((it.m_it.primitive_iterator != 0 and it.m_it.primitive_iterator != 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());
                ++it;
                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());
                ++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());
                ++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());
                ++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());
                ++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());
            }
        }

        SECTION("post-decrement")
        {
            SECTION("null")
            {
                json j(json::value_t::null);
                json::iterator it = j.end();
                CHECK(it.m_it.primitive_iterator == 1);
            }

            SECTION("number")
            {
                json j(17);
                json::iterator it = j.end();
                CHECK(it.m_it.primitive_iterator == 1);
                it--;
                CHECK(it.m_it.primitive_iterator == 0);
                it--;
                CHECK((it.m_it.primitive_iterator != 0 and it.m_it.primitive_iterator != 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());
                it--;
                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());
                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());
                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());
                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());
                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());
            }
        }

        SECTION("pre-decrement")
        {
            SECTION("null")
            {
                json j(json::value_t::null);
                json::iterator it = j.end();
                CHECK(it.m_it.primitive_iterator == 1);
            }

            SECTION("number")
            {
                json j(17);
                json::iterator it = j.end();
                CHECK(it.m_it.primitive_iterator == 1);
                --it;
                CHECK(it.m_it.primitive_iterator == 0);
                --it;
                CHECK((it.m_it.primitive_iterator != 0 and it.m_it.primitive_iterator != 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());
                --it;
                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());
                --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());
                --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());
                --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());
                --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());
            }
        }
    }
}

TEST_CASE("const_iterator class")
{
    SECTION("construction")
    {
        SECTION("constructor")
        {
            SECTION("null")
            {
                json j(json::value_t::null);
                json::const_iterator it(&j);
            }

            SECTION("object")
            {
                json j(json::value_t::object);
                json::const_iterator it(&j);
            }

            SECTION("array")
            {
                json j(json::value_t::array);
                json::const_iterator it(&j);
            }
        }

        SECTION("copy assignment")
        {
            json j(json::value_t::null);
            json::const_iterator it(&j);
            json::const_iterator it2(&j);
            it2 = it;
        }
    }

    SECTION("initialization")
    {
        SECTION("set_begin")
        {
            SECTION("null")
            {
                json j(json::value_t::null);
                json::const_iterator it(&j);
                it.set_begin();
                CHECK(it == j.cbegin());
            }

            SECTION("object")
            {
                json j(json::value_t::object);
                json::const_iterator it(&j);
                it.set_begin();
                CHECK(it == j.cbegin());
            }

            SECTION("array")
            {
                json j(json::value_t::array);
                json::const_iterator it(&j);
                it.set_begin();
                CHECK(it == j.cbegin());
            }
        }

        SECTION("set_end")
        {
            SECTION("null")
            {
                json j(json::value_t::null);
                json::const_iterator it(&j);
                it.set_end();
                CHECK(it == j.cend());
            }

            SECTION("object")
            {
                json j(json::value_t::object);
                json::const_iterator it(&j);
                it.set_end();
                CHECK(it == j.cend());
            }

            SECTION("array")
            {
                json j(json::value_t::array);
                json::const_iterator it(&j);
                it.set_end();
                CHECK(it == j.cend());
            }
        }
    }

    SECTION("element access")
    {
        SECTION("operator*")
        {
            SECTION("null")
            {
                json j(json::value_t::null);
                json::const_iterator it = j.cbegin();
                CHECK_THROWS_AS(*it, std::out_of_range);
                CHECK_THROWS_WITH(*it, "cannot get value");
            }

            SECTION("number")
            {
                json j(17);
                json::const_iterator it = j.cbegin();
                CHECK(*it == json(17));
                it = j.cend();
                CHECK_THROWS_AS(*it, std::out_of_range);
                CHECK_THROWS_WITH(*it, "cannot get value");
            }

            SECTION("object")
            {
                json j({{"foo", "bar"}});
                json::const_iterator it = j.cbegin();
                CHECK(*it == json("bar"));
            }

            SECTION("array")
            {
                json j({1, 2, 3, 4});
                json::const_iterator it = j.cbegin();
                CHECK(*it == json(1));
            }
        }

        SECTION("operator->")
        {
            SECTION("null")
            {
                json j(json::value_t::null);
                json::const_iterator it = j.cbegin();
                CHECK_THROWS_AS(it->type_name(), std::out_of_range);
                CHECK_THROWS_WITH(it->type_name(), "cannot get value");
            }

            SECTION("number")
            {
                json j(17);
                json::const_iterator it = j.cbegin();
                CHECK(it->type_name() == "number");
                it = j.cend();
                CHECK_THROWS_AS(it->type_name(), std::out_of_range);
                CHECK_THROWS_WITH(it->type_name(), "cannot get value");
            }

            SECTION("object")
            {
                json j({{"foo", "bar"}});
                json::const_iterator it = j.cbegin();
                CHECK(it->type_name() == "string");
            }

            SECTION("array")
            {
                json j({1, 2, 3, 4});
                json::const_iterator it = j.cbegin();
                CHECK(it->type_name() == "number");
            }
        }
    }

    SECTION("increment/decrement")
    {
        SECTION("post-increment")
        {
            SECTION("null")
            {
                json j(json::value_t::null);
                json::const_iterator it = j.cbegin();
                CHECK(it.m_it.primitive_iterator == 1);
                it++;
                CHECK((it.m_it.primitive_iterator != 0 and it.m_it.primitive_iterator != 1));
            }

            SECTION("number")
            {
                json j(17);
                json::const_iterator it = j.cbegin();
                CHECK(it.m_it.primitive_iterator == 0);
                it++;
                CHECK(it.m_it.primitive_iterator == 1);
                it++;
                CHECK((it.m_it.primitive_iterator != 0 and it.m_it.primitive_iterator != 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());
                it++;
                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());
                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());
                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());
                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());
                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());
            }
        }

        SECTION("pre-increment")
        {
            SECTION("null")
            {
                json j(json::value_t::null);
                json::const_iterator it = j.cbegin();
                CHECK(it.m_it.primitive_iterator == 1);
                ++it;
                CHECK((it.m_it.primitive_iterator != 0 and it.m_it.primitive_iterator != 1));
            }

            SECTION("number")
            {
                json j(17);
                json::const_iterator it = j.cbegin();
                CHECK(it.m_it.primitive_iterator == 0);
                ++it;
                CHECK(it.m_it.primitive_iterator == 1);
                ++it;
                CHECK((it.m_it.primitive_iterator != 0 and it.m_it.primitive_iterator != 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());
                ++it;
                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());
                ++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());
                ++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());
                ++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());
                ++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());
            }
        }

        SECTION("post-decrement")
        {
            SECTION("null")
            {
                json j(json::value_t::null);
                json::const_iterator it = j.cend();
                CHECK(it.m_it.primitive_iterator == 1);
            }

            SECTION("number")
            {
                json j(17);
                json::const_iterator it = j.cend();
                CHECK(it.m_it.primitive_iterator == 1);
                it--;
                CHECK(it.m_it.primitive_iterator == 0);
                it--;
                CHECK((it.m_it.primitive_iterator != 0 and it.m_it.primitive_iterator != 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());
                it--;
                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());
                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());
                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());
                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());
                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());
            }
        }

        SECTION("pre-decrement")
        {
            SECTION("null")
            {
                json j(json::value_t::null);
                json::const_iterator it = j.cend();
                CHECK(it.m_it.primitive_iterator == 1);
            }

            SECTION("number")
            {
                json j(17);
                json::const_iterator it = j.cend();
                CHECK(it.m_it.primitive_iterator == 1);
                --it;
                CHECK(it.m_it.primitive_iterator == 0);
                --it;
                CHECK((it.m_it.primitive_iterator != 0 and it.m_it.primitive_iterator != 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());
                --it;
                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());
                --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());
                --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());
                --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());
                --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());
            }
        }
    }
}

TEST_CASE("convenience functions")
{
    SECTION("type name as string")
    {
        CHECK(json(json::value_t::null).type_name() == "null");
        CHECK(json(json::value_t::object).type_name() == "object");
        CHECK(json(json::value_t::array).type_name() == "array");
        CHECK(json(json::value_t::number_integer).type_name() == "number");
        CHECK(json(json::value_t::number_float).type_name() == "number");
        CHECK(json(json::value_t::boolean).type_name() == "boolean");
        CHECK(json(json::value_t::string).type_name() == "string");
        CHECK(json(json::value_t::discarded).type_name() == "discarded");
    }

    SECTION("string escape")
    {
        CHECK(json::escape_string("\"") == "\\\"");
        CHECK(json::escape_string("\\") == "\\\\");
        CHECK(json::escape_string("\b") == "\\b");
        CHECK(json::escape_string("\f") == "\\f");
        CHECK(json::escape_string("\n") == "\\n");
        CHECK(json::escape_string("\r") == "\\r");
        CHECK(json::escape_string("\t") == "\\t");

        CHECK(json::escape_string("\x01") == "\\u0001");
        CHECK(json::escape_string("\x02") == "\\u0002");
        CHECK(json::escape_string("\x03") == "\\u0003");
        CHECK(json::escape_string("\x04") == "\\u0004");
        CHECK(json::escape_string("\x05") == "\\u0005");
        CHECK(json::escape_string("\x06") == "\\u0006");
        CHECK(json::escape_string("\x07") == "\\u0007");
        CHECK(json::escape_string("\x08") == "\\b");
        CHECK(json::escape_string("\x09") == "\\t");
        CHECK(json::escape_string("\x0a") == "\\n");
        CHECK(json::escape_string("\x0b") == "\\u000b");
        CHECK(json::escape_string("\x0c") == "\\f");
        CHECK(json::escape_string("\x0d") == "\\r");
        CHECK(json::escape_string("\x0e") == "\\u000e");
        CHECK(json::escape_string("\x0f") == "\\u000f");
        CHECK(json::escape_string("\x10") == "\\u0010");
        CHECK(json::escape_string("\x11") == "\\u0011");
        CHECK(json::escape_string("\x12") == "\\u0012");
        CHECK(json::escape_string("\x13") == "\\u0013");
        CHECK(json::escape_string("\x14") == "\\u0014");
        CHECK(json::escape_string("\x15") == "\\u0015");
        CHECK(json::escape_string("\x16") == "\\u0016");
        CHECK(json::escape_string("\x17") == "\\u0017");
        CHECK(json::escape_string("\x18") == "\\u0018");
        CHECK(json::escape_string("\x19") == "\\u0019");
        CHECK(json::escape_string("\x1a") == "\\u001a");
        CHECK(json::escape_string("\x1b") == "\\u001b");
        CHECK(json::escape_string("\x1c") == "\\u001c");
        CHECK(json::escape_string("\x1d") == "\\u001d");
        CHECK(json::escape_string("\x1e") == "\\u001e");
        CHECK(json::escape_string("\x1f") == "\\u001f");
    }
}

TEST_CASE("lexer class")
{
    SECTION("scan")
    {
        SECTION("structural characters")
        {
            CHECK(json::lexer("[").scan() == json::lexer::token_type::begin_array);
            CHECK(json::lexer("]").scan() == json::lexer::token_type::end_array);
            CHECK(json::lexer("{").scan() == json::lexer::token_type::begin_object);
            CHECK(json::lexer("}").scan() == json::lexer::token_type::end_object);
            CHECK(json::lexer(",").scan() == json::lexer::token_type::value_separator);
            CHECK(json::lexer(":").scan() == json::lexer::token_type::name_separator);
        }

        SECTION("literal names")
        {
            CHECK(json::lexer("null").scan() == json::lexer::token_type::literal_null);
            CHECK(json::lexer("true").scan() == json::lexer::token_type::literal_true);
            CHECK(json::lexer("false").scan() == json::lexer::token_type::literal_false);
        }

        SECTION("numbers")
        {
            CHECK(json::lexer("0").scan() == json::lexer::token_type::value_number);
            CHECK(json::lexer("1").scan() == json::lexer::token_type::value_number);
            CHECK(json::lexer("2").scan() == json::lexer::token_type::value_number);
            CHECK(json::lexer("3").scan() == json::lexer::token_type::value_number);
            CHECK(json::lexer("4").scan() == json::lexer::token_type::value_number);
            CHECK(json::lexer("5").scan() == json::lexer::token_type::value_number);
            CHECK(json::lexer("6").scan() == json::lexer::token_type::value_number);
            CHECK(json::lexer("7").scan() == json::lexer::token_type::value_number);
            CHECK(json::lexer("8").scan() == json::lexer::token_type::value_number);
            CHECK(json::lexer("9").scan() == json::lexer::token_type::value_number);
        }

        SECTION("whitespace")
        {
            // result is end_of_input, because not token is following
            CHECK(json::lexer(" ").scan() == json::lexer::token_type::end_of_input);
            CHECK(json::lexer("\t").scan() == json::lexer::token_type::end_of_input);
            CHECK(json::lexer("\n").scan() == json::lexer::token_type::end_of_input);
            CHECK(json::lexer("\r").scan() == json::lexer::token_type::end_of_input);
            CHECK(json::lexer(" \t\n\r\n\t ").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");
    }

    SECTION("parse errors on first character")
    {
        for (int c = 1; c < 128; ++c)
        {
            auto s = std::string(1, c);

            switch (c)
            {
                // single characters that are valid tokens
                case ('['):
                case (']'):
                case ('{'):
                case ('}'):
                case (','):
                case (':'):
                case ('0'):
                case ('1'):
                case ('2'):
                case ('3'):
                case ('4'):
                case ('5'):
                case ('6'):
                case ('7'):
                case ('8'):
                case ('9'):
                {
                    CHECK(json::lexer(s.c_str()).scan() != json::lexer::token_type::parse_error);
                    break;
                }

                // whitespace
                case (' '):
                case ('\t'):
                case ('\n'):
                case ('\r'):
                {
                    CHECK(json::lexer(s.c_str()).scan() == json::lexer::token_type::end_of_input);
                    break;
                }

                // anything else is not expected
                default:
                {
                    CHECK(json::lexer(s.c_str()).scan() == json::lexer::token_type::parse_error);
                    break;
                }
            }
        }
    }

    SECTION("to_unicode")
    {
        CHECK(json::lexer::to_unicode(0x1F4A9) == "💩");
        CHECK_THROWS_AS(json::lexer::to_unicode(0x200000), std::out_of_range);
        CHECK_THROWS_WITH(json::lexer::to_unicode(0x200000), "code points above 0x10FFFF are invalid");
    }
}

TEST_CASE("parser class")
{
    SECTION("parse")
    {
        SECTION("null")
        {
            CHECK(json::parser("null").parse() == json(nullptr));
        }

        SECTION("true")
        {
            CHECK(json::parser("true").parse() == json(true));
        }

        SECTION("false")
        {
            CHECK(json::parser("false").parse() == json(false));
        }

        SECTION("array")
        {
            SECTION("empty array")
            {
                CHECK(json::parser("[]").parse() == json(json::value_t::array));
                CHECK(json::parser("[ ]").parse() == json(json::value_t::array));
            }

            SECTION("nonempty array")
            {
                CHECK(json::parser("[true, false, null]").parse() == json({true, false, nullptr}));
            }
        }

        SECTION("object")
        {
            SECTION("empty object")
            {
                CHECK(json::parser("{}").parse() == json(json::value_t::object));
                CHECK(json::parser("{ }").parse() == json(json::value_t::object));
            }

            SECTION("nonempty object")
            {
                CHECK(json::parser("{\"\": true, \"one\": 1, \"two\": null}").parse() == json({{"", true}, {"one", 1}, {"two", nullptr}}));
            }
        }

        SECTION("string")
        {
            // empty string
            CHECK(json::parser("\"\"").parse() == json(json::value_t::string));

            SECTION("errors")
            {
                // error: tab in string
                CHECK_THROWS_AS(json::parser("\"\t\"").parse(), std::invalid_argument);
                CHECK_THROWS_WITH(json::parser("\"\t\"").parse(), "parse error - unexpected '\"'");
                // error: newline in string
                CHECK_THROWS_AS(json::parser("\"\n\"").parse(), std::invalid_argument);
                CHECK_THROWS_AS(json::parser("\"\r\"").parse(), std::invalid_argument);
                CHECK_THROWS_WITH(json::parser("\"\n\"").parse(), "parse error - unexpected '\"'");
                CHECK_THROWS_WITH(json::parser("\"\r\"").parse(), "parse error - unexpected '\"'");
                // error: backspace in string
                CHECK_THROWS_AS(json::parser("\"\b\"").parse(), std::invalid_argument);
                CHECK_THROWS_WITH(json::parser("\"\b\"").parse(), "parse error - unexpected '\"'");
                // improve code coverage
                CHECK_THROWS_AS(json::parser("\uFF01").parse(), std::invalid_argument);
            }

            SECTION("escaped")
            {
                // quotation mark "\""
                auto r1 = R"("\"")"_json;
                CHECK(json::parser("\"\\\"\"").parse() == r1);
                // reverse solidus "\\"
                auto r2 = R"("\\")"_json;
                CHECK(json::parser("\"\\\\\"").parse() == r2);
                // solidus
                CHECK(json::parser("\"\\/\"").parse() == R"("/")"_json);
                // backspace
                CHECK(json::parser("\"\\b\"").parse() == json("\b"));
                // formfeed
                CHECK(json::parser("\"\\f\"").parse() == json("\f"));
                // newline
                CHECK(json::parser("\"\\n\"").parse() == json("\n"));
                // carriage return
                CHECK(json::parser("\"\\r\"").parse() == json("\r"));
                // horizontal tab
                CHECK(json::parser("\"\\t\"").parse() == json("\t"));

                CHECK(json::parser("\"\\u0001\"").parse().get<json::string_t>() == "\x01");
                CHECK(json::parser("\"\\u000a\"").parse().get<json::string_t>() == "\n");
                CHECK(json::parser("\"\\u00b0\"").parse().get<json::string_t>() == "°");
                CHECK(json::parser("\"\\u0c00\"").parse().get<json::string_t>() == "ఀ");
                CHECK(json::parser("\"\\ud000\"").parse().get<json::string_t>() == "퀀");
                CHECK(json::parser("\"\\u000E\"").parse().get<json::string_t>() == "\x0E");
                CHECK(json::parser("\"\\u00F0\"").parse().get<json::string_t>() == "ð");
                CHECK(json::parser("\"\\u0100\"").parse().get<json::string_t>() == "Ā");
                CHECK(json::parser("\"\\u2000\"").parse().get<json::string_t>() == " ");
                CHECK(json::parser("\"\\uFFFF\"").parse().get<json::string_t>() == "");
                CHECK(json::parser("\"\\u20AC\"").parse().get<json::string_t>() == "€");
                CHECK(json::parser("\"€\"").parse().get<json::string_t>() == "€");
                CHECK(json::parser("\"🎈\"").parse().get<json::string_t>() == "🎈");

                CHECK(json::parse("\"\\ud80c\\udc60\"").get<json::string_t>() == u8"\U00013060");
                CHECK(json::parse("\"\\ud83c\\udf1e\"").get<json::string_t>() == "🌞");
            }
        }

        SECTION("number")
        {
            SECTION("integers")
            {
                SECTION("without exponent")
                {
                    CHECK(json::parser("-128").parse() == json(-128));
                    CHECK(json::parser("-0").parse() == json(-0));
                    CHECK(json::parser("0").parse() == json(0));
                    CHECK(json::parser("128").parse() == json(128));
                }

                SECTION("with exponent")
                {
                    CHECK(json::parser("0e1").parse() == json(0e1));
                    CHECK(json::parser("0E1").parse() == json(0e1));

                    CHECK(json::parser("10000E-4").parse() == json(10000e-4));
                    CHECK(json::parser("10000E-3").parse() == json(10000e-3));
                    CHECK(json::parser("10000E-2").parse() == json(10000e-2));
                    CHECK(json::parser("10000E-1").parse() == json(10000e-1));
                    CHECK(json::parser("10000E0").parse() == json(10000e0));
                    CHECK(json::parser("10000E1").parse() == json(10000e1));
                    CHECK(json::parser("10000E2").parse() == json(10000e2));
                    CHECK(json::parser("10000E3").parse() == json(10000e3));
                    CHECK(json::parser("10000E4").parse() == json(10000e4));

                    CHECK(json::parser("10000e-4").parse() == json(10000e-4));
                    CHECK(json::parser("10000e-3").parse() == json(10000e-3));
                    CHECK(json::parser("10000e-2").parse() == json(10000e-2));
                    CHECK(json::parser("10000e-1").parse() == json(10000e-1));
                    CHECK(json::parser("10000e0").parse() == json(10000e0));
                    CHECK(json::parser("10000e1").parse() == json(10000e1));
                    CHECK(json::parser("10000e2").parse() == json(10000e2));
                    CHECK(json::parser("10000e3").parse() == json(10000e3));
                    CHECK(json::parser("10000e4").parse() == json(10000e4));

                    CHECK(json::parser("-0e1").parse() == json(-0e1));
                    CHECK(json::parser("-0E1").parse() == json(-0e1));
                    CHECK(json::parser("-0E123").parse() == json(-0e123));
                }

                SECTION("edge cases")
                {
                    // From RFC7159, Section 6:
                    // Note that when such software is used, numbers that are
                    // integers and are in the range [-(2**53)+1, (2**53)-1]
                    // are interoperable in the sense that implementations will
                    // agree exactly on their numeric values.

                    // -(2**53)+1
                    CHECK(json::parser("-9007199254740991").parse().get<int64_t>() == -9007199254740991);
                    // (2**53)-1
                    CHECK(json::parser("9007199254740991").parse().get<int64_t>() == 9007199254740991);
                }

                SECTION("over the edge cases")  // issue #178 - Integer conversion to unsigned (incorrect handling of 64 bit integers)
                {
                    // While RFC7159, Section 6 specifies a preference for support
                    // for ranges in range of IEEE 754-2008 binary64 (double precision)
                    // this does not accommodate 64 bit integers without loss of accuracy.
                    // As 64 bit integers are now widely used in software, it is desirable
                    // to expand support to to the full 64 bit (signed and unsigned) range
                    // i.e. -(2**63) -> (2**64)-1.

                    // -(2**63)    ** Note: compilers see negative literals as negated positive numbers (hence the -1))
                    CHECK(json::parser("-9223372036854775808").parse().get<int64_t>() == -9223372036854775807 - 1);
                    // (2**63)-1
                    CHECK(json::parser("9223372036854775807").parse().get<int64_t>() == 9223372036854775807);
                    // (2**64)-1
                    CHECK(json::parser("18446744073709551615").parse().get<uint64_t>() == 18446744073709551615u);
                }
            }

            SECTION("floating-point")
            {
                SECTION("without exponent")
                {
                    CHECK(json::parser("-128.5").parse() == json(-128.5));
                    CHECK(json::parser("0.999").parse() == json(0.999));
                    CHECK(json::parser("128.5").parse() == json(128.5));
                    CHECK(json::parser("-0.0").parse() == json(-0.0));
                }

                SECTION("with exponent")
                {
                    CHECK(json::parser("-128.5E3").parse() == json(-128.5E3));
                    CHECK(json::parser("-128.5E-3").parse() == json(-128.5E-3));
                    CHECK(json::parser("-0.0e1").parse() == json(-0.0e1));
                    CHECK(json::parser("-0.0E1").parse() == json(-0.0e1));
                }
            }

            SECTION("invalid numbers")
            {
                CHECK_THROWS_AS(json::parser("01").parse(), std::invalid_argument);
                CHECK_THROWS_AS(json::parser("--1").parse(), std::invalid_argument);
                CHECK_THROWS_AS(json::parser("1.").parse(), std::invalid_argument);
                CHECK_THROWS_AS(json::parser("1E").parse(), std::invalid_argument);
                CHECK_THROWS_AS(json::parser("1E-").parse(), std::invalid_argument);
                CHECK_THROWS_AS(json::parser("1.E1").parse(), std::invalid_argument);
                CHECK_THROWS_AS(json::parser("-1E").parse(), std::invalid_argument);
                CHECK_THROWS_AS(json::parser("-0E#").parse(), std::invalid_argument);
                CHECK_THROWS_AS(json::parser("-0E-#").parse(), std::invalid_argument);
                CHECK_THROWS_AS(json::parser("-0#").parse(), std::invalid_argument);
                CHECK_THROWS_AS(json::parser("-0.0:").parse(), std::invalid_argument);
                CHECK_THROWS_AS(json::parser("-0.0Z").parse(), std::invalid_argument);
                CHECK_THROWS_AS(json::parser("-0E123:").parse(), std::invalid_argument);
                CHECK_THROWS_AS(json::parser("-0e0-:").parse(), std::invalid_argument);
                CHECK_THROWS_AS(json::parser("-0e-:").parse(), std::invalid_argument);
                CHECK_THROWS_AS(json::parser("-0f").parse(), std::invalid_argument);

                // numbers must not begin with "+"
                CHECK_THROWS_AS(json::parser("+1").parse(), std::invalid_argument);
                CHECK_THROWS_AS(json::parser("+0").parse(), std::invalid_argument);

                CHECK_THROWS_WITH(json::parser("01").parse(),
                                  "parse error - unexpected number literal; expected end of input");
                CHECK_THROWS_WITH(json::parser("--1").parse(), "parse error - unexpected '-'");
                CHECK_THROWS_WITH(json::parser("1.").parse(),
                                  "parse error - unexpected '.'; expected end of input");
                CHECK_THROWS_WITH(json::parser("1E").parse(),
                                  "parse error - unexpected 'E'; expected end of input");
                CHECK_THROWS_WITH(json::parser("1E-").parse(),
                                  "parse error - unexpected 'E'; expected end of input");
                CHECK_THROWS_WITH(json::parser("1.E1").parse(),
                                  "parse error - unexpected '.'; expected end of input");
                CHECK_THROWS_WITH(json::parser("-1E").parse(),
                                  "parse error - unexpected 'E'; expected end of input");
                CHECK_THROWS_WITH(json::parser("-0E#").parse(),
                                  "parse error - unexpected 'E'; expected end of input");
                CHECK_THROWS_WITH(json::parser("-0E-#").parse(),
                                  "parse error - unexpected 'E'; expected end of input");
                CHECK_THROWS_WITH(json::parser("-0#").parse(),
                                  "parse error - unexpected '#'; expected end of input");
                CHECK_THROWS_WITH(json::parser("-0.0:").parse(),
                                  "parse error - unexpected ':'; expected end of input");
                CHECK_THROWS_WITH(json::parser("-0.0Z").parse(),
                                  "parse error - unexpected 'Z'; expected end of input");
                CHECK_THROWS_WITH(json::parser("-0E123:").parse(),
                                  "parse error - unexpected ':'; expected end of input");
                CHECK_THROWS_WITH(json::parser("-0e0-:").parse(),
                                  "parse error - unexpected '-'; expected end of input");
                CHECK_THROWS_WITH(json::parser("-0e-:").parse(),
                                  "parse error - unexpected 'e'; expected end of input");
                CHECK_THROWS_WITH(json::parser("-0f").parse(),
                                  "parse error - unexpected 'f'; expected end of input");
            }
        }
    }

    SECTION("parse errors")
    {
        // unexpected end of number
        CHECK_THROWS_AS(json::parser("0.").parse(), std::invalid_argument);
        CHECK_THROWS_AS(json::parser("-").parse(), std::invalid_argument);
        CHECK_THROWS_AS(json::parser("--").parse(), std::invalid_argument);
        CHECK_THROWS_AS(json::parser("-0.").parse(), std::invalid_argument);
        CHECK_THROWS_AS(json::parser("-.").parse(), std::invalid_argument);
        CHECK_THROWS_AS(json::parser("-:").parse(), std::invalid_argument);
        CHECK_THROWS_AS(json::parser("0.:").parse(), std::invalid_argument);
        CHECK_THROWS_AS(json::parser("e.").parse(), std::invalid_argument);
        CHECK_THROWS_AS(json::parser("1e.").parse(), std::invalid_argument);
        CHECK_THROWS_AS(json::parser("1e/").parse(), std::invalid_argument);
        CHECK_THROWS_AS(json::parser("1e:").parse(), std::invalid_argument);
        CHECK_THROWS_AS(json::parser("1E.").parse(), std::invalid_argument);
        CHECK_THROWS_AS(json::parser("1E/").parse(), std::invalid_argument);
        CHECK_THROWS_AS(json::parser("1E:").parse(), std::invalid_argument);
        CHECK_THROWS_WITH(json::parser("0.").parse(),
                          "parse error - unexpected '.'; expected end of input");
        CHECK_THROWS_WITH(json::parser("-").parse(), "parse error - unexpected '-'");
        CHECK_THROWS_WITH(json::parser("--").parse(),
                          "parse error - unexpected '-'");
        CHECK_THROWS_WITH(json::parser("-0.").parse(),
                          "parse error - unexpected '.'; expected end of input");
        CHECK_THROWS_WITH(json::parser("-.").parse(),
                          "parse error - unexpected '-'");
        CHECK_THROWS_WITH(json::parser("-:").parse(),
                          "parse error - unexpected '-'");
        CHECK_THROWS_WITH(json::parser("0.:").parse(),
                          "parse error - unexpected '.'; expected end of input");
        CHECK_THROWS_WITH(json::parser("e.").parse(),
                          "parse error - unexpected 'e'");
        CHECK_THROWS_WITH(json::parser("1e.").parse(),
                          "parse error - unexpected 'e'; expected end of input");
        CHECK_THROWS_WITH(json::parser("1e/").parse(),
                          "parse error - unexpected 'e'; expected end of input");
        CHECK_THROWS_WITH(json::parser("1e:").parse(),
                          "parse error - unexpected 'e'; expected end of input");
        CHECK_THROWS_WITH(json::parser("1E.").parse(),
                          "parse error - unexpected 'E'; expected end of input");
        CHECK_THROWS_WITH(json::parser("1E/").parse(),
                          "parse error - unexpected 'E'; expected end of input");
        CHECK_THROWS_WITH(json::parser("1E:").parse(),
                          "parse error - unexpected 'E'; expected end of input");

        // unexpected end of null
        CHECK_THROWS_AS(json::parser("n").parse(), std::invalid_argument);
        CHECK_THROWS_AS(json::parser("nu").parse(), std::invalid_argument);
        CHECK_THROWS_AS(json::parser("nul").parse(), std::invalid_argument);
        CHECK_THROWS_WITH(json::parser("n").parse(), "parse error - unexpected 'n'");
        CHECK_THROWS_WITH(json::parser("nu").parse(),
                          "parse error - unexpected 'n'");
        CHECK_THROWS_WITH(json::parser("nul").parse(),
                          "parse error - unexpected 'n'");

        // unexpected end of true
        CHECK_THROWS_AS(json::parser("t").parse(), std::invalid_argument);
        CHECK_THROWS_AS(json::parser("tr").parse(), std::invalid_argument);
        CHECK_THROWS_AS(json::parser("tru").parse(), std::invalid_argument);
        CHECK_THROWS_WITH(json::parser("t").parse(), "parse error - unexpected 't'");
        CHECK_THROWS_WITH(json::parser("tr").parse(),
                          "parse error - unexpected 't'");
        CHECK_THROWS_WITH(json::parser("tru").parse(),
                          "parse error - unexpected 't'");

        // unexpected end of false
        CHECK_THROWS_AS(json::parser("f").parse(), std::invalid_argument);
        CHECK_THROWS_AS(json::parser("fa").parse(), std::invalid_argument);
        CHECK_THROWS_AS(json::parser("fal").parse(), std::invalid_argument);
        CHECK_THROWS_AS(json::parser("fals").parse(), std::invalid_argument);
        CHECK_THROWS_WITH(json::parser("f").parse(), "parse error - unexpected 'f'");
        CHECK_THROWS_WITH(json::parser("fa").parse(),
                          "parse error - unexpected 'f'");
        CHECK_THROWS_WITH(json::parser("fal").parse(),
                          "parse error - unexpected 'f'");
        CHECK_THROWS_WITH(json::parser("fals").parse(),
                          "parse error - unexpected 'f'");

        // missing/unexpected end of array
        CHECK_THROWS_AS(json::parser("[").parse(), std::invalid_argument);
        CHECK_THROWS_AS(json::parser("[1").parse(), std::invalid_argument);
        CHECK_THROWS_AS(json::parser("[1,").parse(), std::invalid_argument);
        CHECK_THROWS_AS(json::parser("[1,]").parse(), std::invalid_argument);
        CHECK_THROWS_AS(json::parser("]").parse(), std::invalid_argument);
        CHECK_THROWS_WITH(json::parser("[").parse(),
                          "parse error - unexpected end of input");
        CHECK_THROWS_WITH(json::parser("[1").parse(),
                          "parse error - unexpected end of input; expected ']'");
        CHECK_THROWS_WITH(json::parser("[1,").parse(),
                          "parse error - unexpected end of input");
        CHECK_THROWS_WITH(json::parser("[1,]").parse(),
                          "parse error - unexpected ']'");
        CHECK_THROWS_WITH(json::parser("]").parse(), "parse error - unexpected ']'");

        // missing/unexpected end of object
        CHECK_THROWS_AS(json::parser("{").parse(), std::invalid_argument);
        CHECK_THROWS_AS(json::parser("{\"foo\"").parse(), std::invalid_argument);
        CHECK_THROWS_AS(json::parser("{\"foo\":").parse(), std::invalid_argument);
        CHECK_THROWS_AS(json::parser("{\"foo\":}").parse(), std::invalid_argument);
        CHECK_THROWS_AS(json::parser("{\"foo\":1,}").parse(), std::invalid_argument);
        CHECK_THROWS_AS(json::parser("}").parse(), std::invalid_argument);
        CHECK_THROWS_WITH(json::parser("{").parse(),
                          "parse error - unexpected end of input; expected string literal");
        CHECK_THROWS_WITH(json::parser("{\"foo\"").parse(),
                          "parse error - unexpected end of input; expected ':'");
        CHECK_THROWS_WITH(json::parser("{\"foo\":").parse(),
                          "parse error - unexpected end of input");
        CHECK_THROWS_WITH(json::parser("{\"foo\":}").parse(),
                          "parse error - unexpected '}'");
        CHECK_THROWS_WITH(json::parser("{\"foo\":1,}").parse(),
                          "parse error - unexpected '}'; expected string literal");
        CHECK_THROWS_WITH(json::parser("}").parse(), "parse error - unexpected '}'");

        // missing/unexpected end of string
        CHECK_THROWS_AS(json::parser("\"").parse(), std::invalid_argument);
        CHECK_THROWS_AS(json::parser("\"\\\"").parse(), std::invalid_argument);
        CHECK_THROWS_AS(json::parser("\"\\u\"").parse(), std::invalid_argument);
        CHECK_THROWS_AS(json::parser("\"\\u0\"").parse(), std::invalid_argument);
        CHECK_THROWS_AS(json::parser("\"\\u01\"").parse(), std::invalid_argument);
        CHECK_THROWS_AS(json::parser("\"\\u012\"").parse(), std::invalid_argument);
        CHECK_THROWS_WITH(json::parser("\"").parse(),
                          "parse error - unexpected '\"'");
        CHECK_THROWS_WITH(json::parser("\"\\\"").parse(),
                          "parse error - unexpected '\"'");
        CHECK_THROWS_WITH(json::parser("\"\\u\"").parse(),
                          "parse error - unexpected '\"'");
        CHECK_THROWS_WITH(json::parser("\"\\u0\"").parse(),
                          "parse error - unexpected '\"'");
        CHECK_THROWS_WITH(json::parser("\"\\u01\"").parse(),
                          "parse error - unexpected '\"'");
        CHECK_THROWS_WITH(json::parser("\"\\u012\"").parse(),
                          "parse error - unexpected '\"'");

        // invalid escapes
        for (int c = 1; c < 128; ++c)
        {
            auto s = std::string("\"\\") + std::string(1, c) + "\"";

            switch (c)
            {
                // valid escapes
                case ('"'):
                case ('\\'):
                case ('/'):
                case ('b'):
                case ('f'):
                case ('n'):
                case ('r'):
                case ('t'):
                {
                    CHECK_NOTHROW(json::parser(s).parse());
                    break;
                }

                // \u must be followed with four numbers, so we skip it here
                case ('u'):
                {
                    break;
                }

                // any other combination of backslash and character is invalid
                default:
                {
                    CHECK_THROWS_AS(json::parser(s).parse(), std::invalid_argument);
                    CHECK_THROWS_WITH(json::parser(s).parse(), "parse error - unexpected '\"'");
                    break;
                }
            }
        }

        // invalid \uxxxx escapes
        {
            // check whether character is a valid hex character
            const auto valid = [](int c)
            {
                switch (c)
                {
                    case ('0'):
                    case ('1'):
                    case ('2'):
                    case ('3'):
                    case ('4'):
                    case ('5'):
                    case ('6'):
                    case ('7'):
                    case ('8'):
                    case ('9'):
                    case ('a'):
                    case ('b'):
                    case ('c'):
                    case ('d'):
                    case ('e'):
                    case ('f'):
                    case ('A'):
                    case ('B'):
                    case ('C'):
                    case ('D'):
                    case ('E'):
                    case ('F'):
                    {
                        return true;
                    }

                    default:
                    {
                        return false;
                    }
                }
            };

            for (int c = 1; c < 128; ++c)
            {
                std::string s = "\"\\u";

                // create a string with the iterated character at each position
                auto s1 = s + "000" + std::string(1, c) + "\"";
                auto s2 = s + "00" + std::string(1, c) + "0\"";
                auto s3 = s + "0" + std::string(1, c) + "00\"";
                auto s4 = s + std::string(1, c) + "000\"";

                if (valid(c))
                {
                    CHECK_NOTHROW(json::parser(s1).parse());
                    CHECK_NOTHROW(json::parser(s2).parse());
                    CHECK_NOTHROW(json::parser(s3).parse());
                    CHECK_NOTHROW(json::parser(s4).parse());
                }
                else
                {
                    CHECK_THROWS_AS(json::parser(s1).parse(), std::invalid_argument);
                    CHECK_THROWS_AS(json::parser(s2).parse(), std::invalid_argument);
                    CHECK_THROWS_AS(json::parser(s3).parse(), std::invalid_argument);
                    CHECK_THROWS_AS(json::parser(s4).parse(), std::invalid_argument);

                    CHECK_THROWS_WITH(json::parser(s1).parse(), "parse error - unexpected '\"'");
                    CHECK_THROWS_WITH(json::parser(s2).parse(), "parse error - unexpected '\"'");
                    CHECK_THROWS_WITH(json::parser(s3).parse(), "parse error - unexpected '\"'");
                    CHECK_THROWS_WITH(json::parser(s4).parse(), "parse error - unexpected '\"'");
                }
            }
        }

        // missing part of a surrogate pair
        CHECK_THROWS_AS(json::parse("\"\\uD80C\""), std::invalid_argument);
        CHECK_THROWS_WITH(json::parse("\"\\uD80C\""), "missing low surrogate");
        // invalid surrogate pair
        CHECK_THROWS_AS(json::parse("\"\\uD80C\\uD80C\""), std::invalid_argument);
        CHECK_THROWS_AS(json::parse("\"\\uD80C\\u0000\""), std::invalid_argument);
        CHECK_THROWS_AS(json::parse("\"\\uD80C\\uFFFF\""), std::invalid_argument);
        CHECK_THROWS_WITH(json::parse("\"\\uD80C\\uD80C\""),
                          "missing or wrong low surrogate");
        CHECK_THROWS_WITH(json::parse("\"\\uD80C\\u0000\""),
                          "missing or wrong low surrogate");
        CHECK_THROWS_WITH(json::parse("\"\\uD80C\\uFFFF\""),
                          "missing or wrong low surrogate");
    }

    SECTION("callback function")
    {
        auto s_object = R"(
            {
                "foo": 2,
                "bar": {
                    "baz": 1
                }
            }
        )";

        auto s_array = R"(
            [1,2,[3,4,5],4,5]
        )";

        SECTION("filter nothing")
        {
            json j_object = json::parse(s_object, [](int, json::parse_event_t, const json&)
            {
                return true;
            });

            CHECK (j_object == json({{"foo", 2}, {"bar", {{"baz", 1}}}}));

            json j_array = json::parse(s_array, [](int, json::parse_event_t, const json&)
            {
                return true;
            });

            CHECK (j_array == json({1, 2, {3, 4, 5}, 4, 5}));
        }

        SECTION("filter everything")
        {
            json j_object = json::parse(s_object, [](int, json::parse_event_t, const json&)
            {
                return false;
            });

            // the top-level object will be discarded, leaving a null
            CHECK (j_object.is_null());

            json j_array = json::parse(s_array, [](int, json::parse_event_t, const json&)
            {
                return false;
            });

            // the top-level array will be discarded, leaving a null
            CHECK (j_array.is_null());
        }

        SECTION("filter specific element")
        {
            json j_object = json::parse(s_object, [](int, json::parse_event_t, const json & j)
            {
                // filter all number(2) elements
                if (j == json(2))
                {
                    return false;
                }
                else
                {
                    return true;
                }
            });

            CHECK (j_object == json({{"bar", {{"baz", 1}}}}));

            json j_array = json::parse(s_array, [](int, json::parse_event_t, const json & j)
            {
                if (j == json(2))
                {
                    return false;
                }
                else
                {
                    return true;
                }
            });

            CHECK (j_array == json({1, {3, 4, 5}, 4, 5}));
        }

        SECTION("filter specific events")
        {
            SECTION("first closing event")
            {
                {
                    json j_object = json::parse(s_object, [](int, json::parse_event_t e, const json&)
                    {
                        static bool first = true;
                        if (e == json::parse_event_t::object_end and first)
                        {
                            first = false;
                            return false;
                        }
                        else
                        {
                            return true;
                        }
                    });

                    // the first completed object will be discarded
                    CHECK (j_object == json({{"foo", 2}}));
                }

                {
                    json j_array = json::parse(s_array, [](int, json::parse_event_t e, const json&)
                    {
                        static bool first = true;
                        if (e == json::parse_event_t::array_end and first)
                        {
                            first = false;
                            return false;
                        }
                        else
                        {
                            return true;
                        }
                    });

                    // the first completed array will be discarded
                    CHECK (j_array == json({1, 2, 4, 5}));
                }
            }
        }

        SECTION("special cases")
        {
            // the following test cases cover the situation in which an empty
            // object and array is discarded only after the closing character
            // has been read

            json j_empty_object = json::parse("{}", [](int, json::parse_event_t e, const json&)
            {
                if (e == json::parse_event_t::object_end)
                {
                    return false;
                }
                else
                {
                    return true;
                }
            });
            CHECK(j_empty_object == json());

            json j_empty_array = json::parse("[]", [](int, json::parse_event_t e, const json&)
            {
                if (e == json::parse_event_t::array_end)
                {
                    return false;
                }
                else
                {
                    return true;
                }
            });
            CHECK(j_empty_array == json());
        }
    }
}

TEST_CASE("README", "[hide]")
{
    {
        // create an empty structure (null)
        json j;

        // add a number that is stored as double (note the implicit conversion of j to an object)
        j["pi"] = 3.141;

        // add a Boolean that is stored as bool
        j["happy"] = true;

        // add a string that is stored as std::string
        j["name"] = "Niels";

        // add another null object by passing nullptr
        j["nothing"] = nullptr;

        // add an object inside the object
        j["answer"]["everything"] = 42;

        // add an array that is stored as std::vector (using an initializer list)
        j["list"] = { 1, 0, 2 };

        // add another object (using an initializer list of pairs)
        j["object"] = { {"currency", "USD"}, {"value", 42.99} };

        // instead, you could also write (which looks very similar to the JSON above)
        json j2 =
        {
            {"pi", 3.141},
            {"happy", true},
            {"name", "Niels"},
            {"nothing", nullptr},
            {
                "answer", {
                    {"everything", 42}
                }
            },
            {"list", {1, 0, 2}},
            {
                "object", {
                    {"currency", "USD"},
                    {"value", 42.99}
                }
            }
        };
    }

    {
        // ways to express the empty array []
        json empty_array_implicit = {{}};
        json empty_array_explicit = json::array();

        // a way to express the empty object {}
        json empty_object_explicit = json::object();

        // a way to express an _array_ of key/value pairs [["currency", "USD"], ["value", 42.99]]
        json array_not_object = { json::array({"currency", "USD"}), json::array({"value", 42.99}) };
    }

    {
        // create object from string literal
        json j = "{ \"happy\": true, \"pi\": 3.141 }"_json;

        // or even nicer with a raw string literal
        auto j2 = R"(
          {
            "happy": true,
            "pi": 3.141
          }
        )"_json;

        // or explicitly
        auto j3 = json::parse("{ \"happy\": true, \"pi\": 3.141 }");

        // explicit conversion to string
        std::string s = j.dump();    // {\"happy\":true,\"pi\":3.141}

        // serialization with pretty printing
        // pass in the amount of spaces to indent
        std::cout << j.dump(4) << std::endl;
        // {
        //     "happy": true,
        //     "pi": 3.141
        // }

        std::cout << std::setw(2) << j << std::endl;
    }

    {
        // create an array using push_back
        json j;
        j.push_back("foo");
        j.push_back(1);
        j.push_back(true);

        // iterate the array
        for (json::iterator it = j.begin(); it != j.end(); ++it)
        {
            std::cout << *it << '\n';
        }

        // range-based for
        for (auto element : j)
        {
            std::cout << element << '\n';
        }

        // getter/setter
        const std::string tmp = j[0];
        j[1] = 42;
        bool foo = j.at(2);

        // other stuff
        j.size();     // 3 entries
        j.empty();    // false
        j.type();     // json::value_t::array
        j.clear();    // the array is empty again

        // comparison
        j == "[\"foo\", 1, true]"_json;  // true

        // create an object
        json o;
        o["foo"] = 23;
        o["bar"] = false;
        o["baz"] = 3.141;

        // find an entry
        if (o.find("foo") != o.end())
        {
            // there is an entry with key "foo"
        }
    }

    {
        std::vector<int> c_vector {1, 2, 3, 4};
        json j_vec(c_vector);
        // [1, 2, 3, 4]

        std::deque<float> c_deque {1.2f, 2.3f, 3.4f, 5.6f};
        json j_deque(c_deque);
        // [1.2, 2.3, 3.4, 5.6]

        std::list<bool> c_list {true, true, false, true};
        json j_list(c_list);
        // [true, true, false, true]

        std::forward_list<int64_t> c_flist {12345678909876, 23456789098765, 34567890987654, 45678909876543};
        json j_flist(c_flist);
        // [12345678909876, 23456789098765, 34567890987654, 45678909876543]

        std::array<unsigned long, 4> c_array {{1, 2, 3, 4}};
        json j_array(c_array);
        // [1, 2, 3, 4]

        std::set<std::string> c_set {"one", "two", "three", "four", "one"};
        json j_set(c_set); // only one entry for "one" is used
        // ["four", "one", "three", "two"]

        std::unordered_set<std::string> c_uset {"one", "two", "three", "four", "one"};
        json j_uset(c_uset); // only one entry for "one" is used
        // maybe ["two", "three", "four", "one"]

        std::multiset<std::string> c_mset {"one", "two", "one", "four"};
        json j_mset(c_mset); // only one entry for "one" is used
        // maybe ["one", "two", "four"]

        std::unordered_multiset<std::string> c_umset {"one", "two", "one", "four"};
        json j_umset(c_umset); // both entries for "one" are used
        // maybe ["one", "two", "one", "four"]
    }

    {
        std::map<std::string, int> c_map { {"one", 1}, {"two", 2}, {"three", 3} };
        json j_map(c_map);
        // {"one": 1, "two": 2, "three": 3}

        std::unordered_map<const char*, float> c_umap { {"one", 1.2f}, {"two", 2.3f}, {"three", 3.4f} };
        json j_umap(c_umap);
        // {"one": 1.2, "two": 2.3, "three": 3.4}

        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
        // maybe {"one": true, "two": true, "three": true}

        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
        // maybe {"one": true, "two": true, "three": true}
    }

    {
        // strings
        std::string s1 = "Hello, world!";
        json js = s1;
        std::string s2 = js;

        // Booleans
        bool b1 = true;
        json jb = b1;
        bool b2 = jb;

        // numbers
        int i = 42;
        json jn = i;
        double f = jn;

        // etc.

        std::string vs = js.get<std::string>();
        bool vb = jb.get<bool>();
        int vi = jn.get<int>();

        // etc.
    }

    {
        // a JSON value
        json j_original = R"({
          "baz": ["one", "two", "three"],
          "foo": "bar"
        })"_json;

        // access members with a JSON pointer (RFC 6901)
        j_original["/baz/1"_json_pointer];
        // "two"

        // a JSON patch (RFC 6902)
        json j_patch = R"([
          { "op": "replace", "path": "/baz", "value": "boo" },
          { "op": "add", "path": "/hello", "value": ["world"] },
          { "op": "remove", "path": "/foo"}
        ])"_json;

        // apply the patch
        json j_result = j_original.patch(j_patch);
        // {
        //    "baz": "boo",
        //    "hello": ["world"]
        // }

        // calculate a JSON patch from two JSON values
        json::diff(j_result, j_original);
        // [
        //   { "op":" replace", "path": "/baz", "value": ["one", "two", "three"] },
        //   { "op":"remove","path":"/hello" },
        //   { "op":"add","path":"/foo","value":"bar" }
        // ]
    }
}

TEST_CASE("algorithms")
{
    json j_array = {13, 29, 3, {{"one", 1}, {"two", 2}}, true, false, {1, 2, 3}, "foo", "baz"};
    json j_object = {{"one", 1}, {"two", 2}};

    SECTION("non-modifying sequence operations")
    {
        SECTION("std::all_of")
        {
            CHECK(std::all_of(j_array.begin(), j_array.end(), [](const json & value)
            {
                return value.size() > 0;
            }));
            CHECK(std::all_of(j_object.begin(), j_object.end(), [](const json & value)
            {
                return value.type() == json::value_t::number_integer;
            }));
        }

        SECTION("std::any_of")
        {
            CHECK(std::any_of(j_array.begin(), j_array.end(), [](const json & value)
            {
                return value.is_string() and value.get<std::string>() == "foo";
            }));
            CHECK(std::any_of(j_object.begin(), j_object.end(), [](const json & value)
            {
                return value.get<int>() > 1;
            }));
        }

        SECTION("std::none_of")
        {
            CHECK(std::none_of(j_array.begin(), j_array.end(), [](const json & value)
            {
                return value.size() == 0;
            }));
            CHECK(std::none_of(j_object.begin(), j_object.end(), [](const json & value)
            {
                return value.get<int>() <= 0;
            }));
        }

        SECTION("std::for_each")
        {
            SECTION("reading")
            {
                int sum = 0;

                std::for_each(j_array.cbegin(), j_array.cend(), [&sum](const json & value)
                {
                    if (value.is_number())
                    {
                        sum += static_cast<int>(value);
                    }
                });

                CHECK(sum == 45);
            }

            SECTION("writing")
            {
                auto add17 = [](json & value)
                {
                    if (value.is_array())
                    {
                        value.push_back(17);
                    }
                };

                std::for_each(j_array.begin(), j_array.end(), add17);

                CHECK(j_array[6] == json({1, 2, 3, 17}));
            }
        }

        SECTION("std::count")
        {
            CHECK(std::count(j_array.begin(), j_array.end(), json(true)) == 1);
        }

        SECTION("std::count_if")
        {
            CHECK(std::count_if(j_array.begin(), j_array.end(), [](const json & value)
            {
                return (value.is_number());
            }) == 3);
            CHECK(std::count_if(j_array.begin(), j_array.end(), [](const json&)
            {
                return true;
            }) == 9);
        }

        SECTION("std::mismatch")
        {
            json j_array2 = {13, 29, 3, {{"one", 1}, {"two", 2}, {"three", 3}}, true, false, {1, 2, 3}, "foo", "baz"};
            auto res = std::mismatch(j_array.begin(), j_array.end(), j_array2.begin());
            CHECK(*res.first == json({{"one", 1}, {"two", 2}}));
            CHECK(*res.second == json({{"one", 1}, {"two", 2}, {"three", 3}}));
        }

        SECTION("std::equal")
        {
            SECTION("using operator==")
            {
                CHECK(std::equal(j_array.begin(), j_array.end(), j_array.begin()));
                CHECK(std::equal(j_object.begin(), j_object.end(), j_object.begin()));
                CHECK(not std::equal(j_array.begin(), j_array.end(), j_object.begin()));
            }

            SECTION("using user-defined comparison")
            {
                // compare objects only by size of its elements
                json j_array2 = {13, 29, 3, {"Hello", "World"}, true, false, {{"one", 1}, {"two", 2}, {"three", 3}}, "foo", "baz"};
                CHECK(not std::equal(j_array.begin(), j_array.end(), j_array2.begin()));
                CHECK(std::equal(j_array.begin(), j_array.end(), j_array2.begin(),
                                 [](const json & a, const json & b)
                {
                    return (a.size() == b.size());
                }));
            }
        }

        SECTION("std::find")
        {
            auto it = std::find(j_array.begin(), j_array.end(), json(false));
            CHECK(std::distance(j_array.begin(), it) == 5);
        }

        SECTION("std::find_if")
        {
            auto it = std::find_if(j_array.begin(), j_array.end(),
                                   [](const json & value)
            {
                return value.is_boolean();
            });
            CHECK(std::distance(j_array.begin(), it) == 4);
        }

        SECTION("std::find_if_not")
        {
            auto it = std::find_if_not(j_array.begin(), j_array.end(),
                                       [](const json & value)
            {
                return value.is_number();
            });
            CHECK(std::distance(j_array.begin(), it) == 3);
        }

        SECTION("std::adjacent_find")
        {
            CHECK(std::adjacent_find(j_array.begin(), j_array.end()) == j_array.end());
            CHECK(std::adjacent_find(j_array.begin(), j_array.end(),
                                     [](const json & v1, const json & v2)
            {
                return v1.type() == v2.type();
            }) == j_array.begin());
        }
    }

    SECTION("modifying sequence operations")
    {
        SECTION("std::reverse")
        {
            std::reverse(j_array.begin(), j_array.end());
            CHECK(j_array == json({"baz", "foo", {1, 2, 3}, false, true, {{"one", 1}, {"two", 2}}, 3, 29, 13}));
        }

        SECTION("std::rotate")
        {
            std::rotate(j_array.begin(), j_array.begin() + 1, j_array.end());
            CHECK(j_array == json({29, 3, {{"one", 1}, {"two", 2}}, true, false, {1, 2, 3}, "foo", "baz", 13}));
        }

        SECTION("std::partition")
        {
            auto it = std::partition(j_array.begin(), j_array.end(), [](const json & v)
            {
                return v.is_string();
            });
            CHECK(std::distance(j_array.begin(), it) == 2);
            CHECK(not it[2].is_string());
        }
    }

    SECTION("sorting operations")
    {
        SECTION("std::sort")
        {
            SECTION("with standard comparison")
            {
                json j = {13, 29, 3, {{"one", 1}, {"two", 2}}, true, false, {1, 2, 3}, "foo", "baz", nullptr};
                std::sort(j.begin(), j.end());
                CHECK(j == json({nullptr, false, true, 3, 13, 29, {{"one", 1}, {"two", 2}}, {1, 2, 3}, "baz", "foo"}));
            }

            SECTION("with user-defined comparison")
            {
                json j = {3, {{"one", 1}, {"two", 2}}, {1, 2, 3}, nullptr};
                std::sort(j.begin(), j.end(), [](const json & a, const json & b)
                {
                    return a.size() < b.size();
                });
                CHECK(j == json({nullptr, 3, {{"one", 1}, {"two", 2}}, {1, 2, 3}}));
            }

            SECTION("sorting an object")
            {
                json j({{"one", 1}, {"two", 2}});
                CHECK_THROWS_AS(std::sort(j.begin(), j.end()), std::domain_error);
                CHECK_THROWS_WITH(std::sort(j.begin(), j.end()), "cannot use offsets with object iterators");
            }
        }

        SECTION("std::partial_sort")
        {
            json j = {13, 29, 3, {{"one", 1}, {"two", 2}}, true, false, {1, 2, 3}, "foo", "baz", nullptr};
            std::partial_sort(j.begin(), j.begin() + 4, j.end());
            CHECK(j == json({nullptr, false, true, 3, {{"one", 1}, {"two", 2}}, 29, {1, 2, 3}, "foo", "baz", 13}));
        }
    }

    SECTION("set operations")
    {
        SECTION("std::merge")
        {
            {
                json j1 = {2, 4, 6, 8};
                json j2 = {1, 2, 3, 5, 7};
                json j3;

                std::merge(j1.begin(), j1.end(), j2.begin(), j2.end(), std::back_inserter(j3));
                CHECK(j3 == json({1, 2, 2, 3, 4, 5, 6, 7, 8}));
            }
        }

        SECTION("std::set_difference")
        {
            json j1 = {1, 2, 3, 4, 5, 6, 7, 8};
            json j2 = {1, 2, 3, 5, 7};
            json j3;

            std::set_difference(j1.begin(), j1.end(), j2.begin(), j2.end(), std::back_inserter(j3));
            CHECK(j3 == json({4, 6, 8}));
        }

        SECTION("std::set_intersection")
        {
            json j1 = {1, 2, 3, 4, 5, 6, 7, 8};
            json j2 = {1, 2, 3, 5, 7};
            json j3;

            std::set_intersection(j1.begin(), j1.end(), j2.begin(), j2.end(), std::back_inserter(j3));
            CHECK(j3 == json({1, 2, 3, 5, 7}));
        }

        SECTION("std::set_union")
        {
            json j1 = {2, 4, 6, 8};
            json j2 = {1, 2, 3, 5, 7};
            json j3;

            std::set_union(j1.begin(), j1.end(), j2.begin(), j2.end(), std::back_inserter(j3));
            CHECK(j3 == json({1, 2, 3, 4, 5, 6, 7, 8}));
        }

        SECTION("std::set_symmetric_difference")
        {
            json j1 = {2, 4, 6, 8};
            json j2 = {1, 2, 3, 5, 7};
            json j3;

            std::set_symmetric_difference(j1.begin(), j1.end(), j2.begin(), j2.end(), std::back_inserter(j3));
            CHECK(j3 == json({1, 3, 4, 5, 6, 7, 8}));
        }
    }

    SECTION("heap operations")
    {
        std::make_heap(j_array.begin(), j_array.end());
        CHECK(std::is_heap(j_array.begin(), j_array.end()));
        std::sort_heap(j_array.begin(), j_array.end());
        CHECK(j_array == json({false, true, 3, 13, 29, {{"one", 1}, {"two", 2}}, {1, 2, 3}, "baz", "foo"}));
    }
}

TEST_CASE("concepts")
{
    SECTION("container requirements for json")
    {
        // X: container class: json
        // T: type of objects: json
        // a, b: values of type X: json

        // TABLE 96 - Container Requirements

        // X::value_type must return T
        CHECK((std::is_same<json::value_type, json>::value));

        // X::reference must return lvalue of T
        CHECK((std::is_same<json::reference, json&>::value));

        // X::const_reference must return const lvalue of T
        CHECK((std::is_same<json::const_reference, const json&>::value));

        // X::iterator must return iterator whose value_type is T
        CHECK((std::is_same<json::iterator::value_type, json>::value));
        // X::iterator must meet the forward iterator requirements
        CHECK((std::is_base_of<std::forward_iterator_tag, typename std::iterator_traits<json::iterator>::iterator_category>::value));
        // X::iterator must be convertible to X::const_iterator
        CHECK((std::is_convertible<json::iterator, json::const_iterator>::value));

        // X::const_iterator must return iterator whose value_type is T
        CHECK((std::is_same<json::const_iterator::value_type, json>::value));
        // X::const_iterator must meet the forward iterator requirements
        CHECK((std::is_base_of<std::forward_iterator_tag, typename std::iterator_traits<json::const_iterator>::iterator_category>::value));

        // X::difference_type must return a signed integer
        CHECK((std::is_signed<json::difference_type>::value));
        // X::difference_type must be identical to X::iterator::difference_type
        CHECK((std::is_same<json::difference_type, json::iterator::difference_type>::value));
        // X::difference_type must be identical to X::const_iterator::difference_type
        CHECK((std::is_same<json::difference_type, json::const_iterator::difference_type>::value));

        // X::size_type must return an unsigned integer
        CHECK((std::is_unsigned<json::size_type>::value));
        // X::size_type can represent any non-negative value of X::difference_type
        CHECK(std::numeric_limits<json::difference_type>::max() <=
              std::numeric_limits<json::size_type>::max());

        // the expression "X u" has the post-condition "u.empty()"
        {
            json u;
            CHECK(u.empty());
        }

        // the expression "X()" has the post-condition "X().empty()"
        CHECK(json().empty());
    }

    SECTION("class json")
    {
        SECTION("DefaultConstructible")
        {
            CHECK(std::is_nothrow_default_constructible<json>::value);
        }

        SECTION("MoveConstructible")
        {
            CHECK(std::is_nothrow_move_constructible<json>::value);
        }

        SECTION("CopyConstructible")
        {
            CHECK(std::is_copy_constructible<json>::value);
        }

        SECTION("MoveAssignable")
        {
            CHECK(std::is_nothrow_move_assignable<json>::value);
        }

        SECTION("CopyAssignable")
        {
            CHECK(std::is_copy_assignable<json>::value);
        }

        SECTION("Destructible")
        {
            CHECK(std::is_nothrow_destructible<json>::value);
        }

        SECTION("StandardLayoutType")
        {
            CHECK(std::is_standard_layout<json>::value);
        }
    }

    SECTION("class iterator")
    {
        SECTION("CopyConstructible")
        {
            CHECK(std::is_nothrow_copy_constructible<json::iterator>::value);
            CHECK(std::is_nothrow_copy_constructible<json::const_iterator>::value);
        }

        SECTION("CopyAssignable")
        {
            // STL iterators used by json::iterator don't pass this test in Debug mode
#if !defined(_MSC_VER) || (_ITERATOR_DEBUG_LEVEL == 0)
            CHECK(std::is_nothrow_copy_assignable<json::iterator>::value);
            CHECK(std::is_nothrow_copy_assignable<json::const_iterator>::value);
#endif
        }

        SECTION("Destructible")
        {
            CHECK(std::is_nothrow_destructible<json::iterator>::value);
            CHECK(std::is_nothrow_destructible<json::const_iterator>::value);
        }

        SECTION("Swappable")
        {
            {
                json j {1, 2, 3};
                json::iterator it1 = j.begin();
                json::iterator it2 = j.end();
                std::swap(it1, it2);
                CHECK(it1 == j.end());
                CHECK(it2 == j.begin());
            }
            {
                json j {1, 2, 3};
                json::const_iterator it1 = j.cbegin();
                json::const_iterator it2 = j.cend();
                std::swap(it1, it2);
                CHECK(it1 == j.end());
                CHECK(it2 == j.begin());
            }
        }
    }
}

TEST_CASE("iterator_wrapper")
{
    SECTION("object")
    {
        SECTION("value")
        {
            json j = {{"A", 1}, {"B", 2}};
            int counter = 1;

            for (auto i : json::iterator_wrapper(j))
            {
                switch (counter++)
                {
                    case 1:
                    {
                        CHECK(i.key() == "A");
                        CHECK(i.value() == json(1));
                        break;
                    }

                    case 2:
                    {
                        CHECK(i.key() == "B");
                        CHECK(i.value() == json(2));
                        break;
                    }

                    default:
                    {
                        break;
                    }
                }
            }

            CHECK(counter == 3);
        }

        SECTION("reference")
        {
            json j = {{"A", 1}, {"B", 2}};
            int counter = 1;

            for (auto& i : json::iterator_wrapper(j))
            {
                switch (counter++)
                {
                    case 1:
                    {
                        CHECK(i.key() == "A");
                        CHECK(i.value() == json(1));

                        // change the value
                        i.value() = json(11);
                        CHECK(i.value() == json(11));
                        break;
                    }

                    case 2:
                    {
                        CHECK(i.key() == "B");
                        CHECK(i.value() == json(2));

                        // change the value
                        i.value() = json(22);
                        CHECK(i.value() == json(22));
                        break;
                    }

                    default:
                    {
                        break;
                    }
                }
            }

            CHECK(counter == 3);

            // check if values where changed
            CHECK(j == json({{"A", 11}, {"B", 22}}));
        }

        SECTION("const value")
        {
            json j = {{"A", 1}, {"B", 2}};
            int counter = 1;

            for (const auto i : json::iterator_wrapper(j))
            {
                switch (counter++)
                {
                    case 1:
                    {
                        CHECK(i.key() == "A");
                        CHECK(i.value() == json(1));
                        break;
                    }

                    case 2:
                    {
                        CHECK(i.key() == "B");
                        CHECK(i.value() == json(2));
                        break;
                    }

                    default:
                    {
                        break;
                    }
                }
            }

            CHECK(counter == 3);
        }

        SECTION("const reference")
        {
            json j = {{"A", 1}, {"B", 2}};
            int counter = 1;

            for (const auto& i : json::iterator_wrapper(j))
            {
                switch (counter++)
                {
                    case 1:
                    {
                        CHECK(i.key() == "A");
                        CHECK(i.value() == json(1));
                        break;
                    }

                    case 2:
                    {
                        CHECK(i.key() == "B");
                        CHECK(i.value() == json(2));
                        break;
                    }

                    default:
                    {
                        break;
                    }
                }
            }

            CHECK(counter == 3);
        }
    }

    SECTION("const object")
    {
        SECTION("value")
        {
            const json j = {{"A", 1}, {"B", 2}};
            int counter = 1;

            for (auto i : json::iterator_wrapper(j))
            {
                switch (counter++)
                {
                    case 1:
                    {
                        CHECK(i.key() == "A");
                        CHECK(i.value() == json(1));
                        break;
                    }

                    case 2:
                    {
                        CHECK(i.key() == "B");
                        CHECK(i.value() == json(2));
                        break;
                    }

                    default:
                    {
                        break;
                    }
                }
            }

            CHECK(counter == 3);
        }

        SECTION("reference")
        {
            const json j = {{"A", 1}, {"B", 2}};
            int counter = 1;

            for (auto& i : json::iterator_wrapper(j))
            {
                switch (counter++)
                {
                    case 1:
                    {
                        CHECK(i.key() == "A");
                        CHECK(i.value() == json(1));
                        break;
                    }

                    case 2:
                    {
                        CHECK(i.key() == "B");
                        CHECK(i.value() == json(2));
                        break;
                    }

                    default:
                    {
                        break;
                    }
                }
            }

            CHECK(counter == 3);
        }

        SECTION("const value")
        {
            const json j = {{"A", 1}, {"B", 2}};
            int counter = 1;

            for (const auto i : json::iterator_wrapper(j))
            {
                switch (counter++)
                {
                    case 1:
                    {
                        CHECK(i.key() == "A");
                        CHECK(i.value() == json(1));
                        break;
                    }

                    case 2:
                    {
                        CHECK(i.key() == "B");
                        CHECK(i.value() == json(2));
                        break;
                    }

                    default:
                    {
                        break;
                    }
                }
            }

            CHECK(counter == 3);
        }

        SECTION("const reference")
        {
            const json j = {{"A", 1}, {"B", 2}};
            int counter = 1;

            for (const auto& i : json::iterator_wrapper(j))
            {
                switch (counter++)
                {
                    case 1:
                    {
                        CHECK(i.key() == "A");
                        CHECK(i.value() == json(1));
                        break;
                    }

                    case 2:
                    {
                        CHECK(i.key() == "B");
                        CHECK(i.value() == json(2));
                        break;
                    }

                    default:
                    {
                        break;
                    }
                }
            }

            CHECK(counter == 3);
        }
    }

    SECTION("array")
    {
        SECTION("value")
        {
            json j = {"A", "B"};
            int counter = 1;

            for (auto i : json::iterator_wrapper(j))
            {
                switch (counter++)
                {
                    case 1:
                    {
                        CHECK(i.key() == "0");
                        CHECK(i.value() == "A");
                        break;
                    }

                    case 2:
                    {
                        CHECK(i.key() == "1");
                        CHECK(i.value() == "B");
                        break;
                    }

                    default:
                    {
                        break;
                    }
                }
            }

            CHECK(counter == 3);
        }

        SECTION("reference")
        {
            json j = {"A", "B"};
            int counter = 1;

            for (auto& i : json::iterator_wrapper(j))
            {
                switch (counter++)
                {
                    case 1:
                    {
                        CHECK(i.key() == "0");
                        CHECK(i.value() == "A");

                        // change the value
                        i.value() = "AA";
                        CHECK(i.value() == "AA");
                        break;
                    }

                    case 2:
                    {
                        CHECK(i.key() == "1");
                        CHECK(i.value() == "B");

                        // change the value
                        i.value() = "BB";
                        CHECK(i.value() == "BB");
                        break;
                    }

                    default:
                    {
                        break;
                    }
                }
            }

            CHECK(counter == 3);

            // check if values where changed
            CHECK(j == json({"AA", "BB"}));
        }

        SECTION("const value")
        {
            json j = {"A", "B"};
            int counter = 1;

            for (const auto i : json::iterator_wrapper(j))
            {
                switch (counter++)
                {
                    case 1:
                    {
                        CHECK(i.key() == "0");
                        CHECK(i.value() == "A");
                        break;
                    }

                    case 2:
                    {
                        CHECK(i.key() == "1");
                        CHECK(i.value() == "B");
                        break;
                    }

                    default:
                    {
                        break;
                    }
                }
            }

            CHECK(counter == 3);
        }

        SECTION("const reference")
        {
            json j = {"A", "B"};
            int counter = 1;

            for (const auto& i : json::iterator_wrapper(j))
            {
                switch (counter++)
                {
                    case 1:
                    {
                        CHECK(i.key() == "0");
                        CHECK(i.value() == "A");
                        break;
                    }

                    case 2:
                    {
                        CHECK(i.key() == "1");
                        CHECK(i.value() == "B");
                        break;
                    }

                    default:
                    {
                        break;
                    }
                }
            }

            CHECK(counter == 3);
        }
    }

    SECTION("const array")
    {
        SECTION("value")
        {
            const json j = {"A", "B"};
            int counter = 1;

            for (auto i : json::iterator_wrapper(j))
            {
                switch (counter++)
                {
                    case 1:
                    {
                        CHECK(i.key() == "0");
                        CHECK(i.value() == "A");
                        break;
                    }

                    case 2:
                    {
                        CHECK(i.key() == "1");
                        CHECK(i.value() == "B");
                        break;
                    }

                    default:
                    {
                        break;
                    }
                }
            }

            CHECK(counter == 3);
        }

        SECTION("reference")
        {
            const json j = {"A", "B"};
            int counter = 1;

            for (auto& i : json::iterator_wrapper(j))
            {
                switch (counter++)
                {
                    case 1:
                    {
                        CHECK(i.key() == "0");
                        CHECK(i.value() == "A");
                        break;
                    }

                    case 2:
                    {
                        CHECK(i.key() == "1");
                        CHECK(i.value() == "B");
                        break;
                    }

                    default:
                    {
                        break;
                    }
                }
            }

            CHECK(counter == 3);
        }

        SECTION("const value")
        {
            const json j = {"A", "B"};
            int counter = 1;

            for (const auto i : json::iterator_wrapper(j))
            {
                switch (counter++)
                {
                    case 1:
                    {
                        CHECK(i.key() == "0");
                        CHECK(i.value() == "A");
                        break;
                    }

                    case 2:
                    {
                        CHECK(i.key() == "1");
                        CHECK(i.value() == "B");
                        break;
                    }

                    default:
                    {
                        break;
                    }
                }
            }

            CHECK(counter == 3);
        }

        SECTION("const reference")
        {
            const json j = {"A", "B"};
            int counter = 1;

            for (const auto& i : json::iterator_wrapper(j))
            {
                switch (counter++)
                {
                    case 1:
                    {
                        CHECK(i.key() == "0");
                        CHECK(i.value() == "A");
                        break;
                    }

                    case 2:
                    {
                        CHECK(i.key() == "1");
                        CHECK(i.value() == "B");
                        break;
                    }

                    default:
                    {
                        break;
                    }
                }
            }

            CHECK(counter == 3);
        }
    }

    SECTION("primitive")
    {
        SECTION("value")
        {
            json j = 1;
            int counter = 1;

            for (auto i : json::iterator_wrapper(j))
            {
                ++counter;
                CHECK(i.key() == "");
                CHECK(i.value() == json(1));
            }

            CHECK(counter == 2);
        }

        SECTION("reference")
        {
            json j = 1;
            int counter = 1;

            for (auto& i : json::iterator_wrapper(j))
            {
                ++counter;
                CHECK(i.key() == "");
                CHECK(i.value() == json(1));

                // change value
                i.value() = json(2);
            }

            CHECK(counter == 2);

            // check if value has changed
            CHECK(j == json(2));
        }

        SECTION("const value")
        {
            json j = 1;
            int counter = 1;

            for (const auto i : json::iterator_wrapper(j))
            {
                ++counter;
                CHECK(i.key() == "");
                CHECK(i.value() == json(1));
            }

            CHECK(counter == 2);
        }

        SECTION("const reference")
        {
            json j = 1;
            int counter = 1;

            for (const auto& i : json::iterator_wrapper(j))
            {
                ++counter;
                CHECK(i.key() == "");
                CHECK(i.value() == json(1));
            }

            CHECK(counter == 2);
        }
    }

    SECTION("const primitive")
    {
        SECTION("value")
        {
            const json j = 1;
            int counter = 1;

            for (auto i : json::iterator_wrapper(j))
            {
                ++counter;
                CHECK(i.key() == "");
                CHECK(i.value() == json(1));
            }

            CHECK(counter == 2);
        }

        SECTION("reference")
        {
            const json j = 1;
            int counter = 1;

            for (auto& i : json::iterator_wrapper(j))
            {
                ++counter;
                CHECK(i.key() == "");
                CHECK(i.value() == json(1));
            }

            CHECK(counter == 2);
        }

        SECTION("const value")
        {
            const json j = 1;
            int counter = 1;

            for (const auto i : json::iterator_wrapper(j))
            {
                ++counter;
                CHECK(i.key() == "");
                CHECK(i.value() == json(1));
            }

            CHECK(counter == 2);
        }

        SECTION("const reference")
        {
            const json j = 1;
            int counter = 1;

            for (const auto& i : json::iterator_wrapper(j))
            {
                ++counter;
                CHECK(i.key() == "");
                CHECK(i.value() == json(1));
            }

            CHECK(counter == 2);
        }
    }
}

TEST_CASE("compliance tests from json.org")
{
    // test cases are from http://json.org/JSON_checker/

    SECTION("expected failures")
    {
        for (auto filename :
                {
                    //"test/data/json_tests/fail1.json",
                    "test/data/json_tests/fail2.json",
                    "test/data/json_tests/fail3.json",
                    "test/data/json_tests/fail4.json",
                    "test/data/json_tests/fail5.json",
                    "test/data/json_tests/fail6.json",
                    "test/data/json_tests/fail7.json",
                    "test/data/json_tests/fail8.json",
                    "test/data/json_tests/fail9.json",
                    "test/data/json_tests/fail10.json",
                    "test/data/json_tests/fail11.json",
                    "test/data/json_tests/fail12.json",
                    "test/data/json_tests/fail13.json",
                    "test/data/json_tests/fail14.json",
                    "test/data/json_tests/fail15.json",
                    "test/data/json_tests/fail16.json",
                    "test/data/json_tests/fail17.json",
                    //"test/data/json_tests/fail18.json",
                    "test/data/json_tests/fail19.json",
                    "test/data/json_tests/fail20.json",
                    "test/data/json_tests/fail21.json",
                    "test/data/json_tests/fail22.json",
                    "test/data/json_tests/fail23.json",
                    "test/data/json_tests/fail24.json",
                    "test/data/json_tests/fail25.json",
                    "test/data/json_tests/fail26.json",
                    "test/data/json_tests/fail27.json",
                    "test/data/json_tests/fail28.json",
                    "test/data/json_tests/fail29.json",
                    "test/data/json_tests/fail30.json",
                    "test/data/json_tests/fail31.json",
                    "test/data/json_tests/fail32.json",
                    "test/data/json_tests/fail33.json"
                })
        {
            CAPTURE(filename);
            json j;
            std::ifstream f(filename);
            CHECK_THROWS_AS(j << f, std::invalid_argument);
        }
    }

    SECTION("expected passes")
    {
        for (auto filename :
                {
                    "test/data/json_tests/pass1.json",
                    "test/data/json_tests/pass2.json",
                    "test/data/json_tests/pass3.json"
                })
        {
            CAPTURE(filename);
            json j;
            std::ifstream f(filename);
            CHECK_NOTHROW(j << f);
        }
    }
}

TEST_CASE("compliance tests from nativejson-benchmark")
{
    // test cases from https://github.com/miloyip/nativejson-benchmark/blob/master/src/main.cpp

    SECTION("doubles")
    {
        auto TEST_DOUBLE = [](const std::string & json_string, const double expected)
        {
            CAPTURE(json_string);
            CAPTURE(expected);
            CHECK(json::parse(json_string)[0].get<double>() == Approx(expected));
        };

        TEST_DOUBLE("[0.0]", 0.0);
        TEST_DOUBLE("[-0.0]", -0.0);
        TEST_DOUBLE("[1.0]", 1.0);
        TEST_DOUBLE("[-1.0]", -1.0);
        TEST_DOUBLE("[1.5]", 1.5);
        TEST_DOUBLE("[-1.5]", -1.5);
        TEST_DOUBLE("[3.1416]", 3.1416);
        TEST_DOUBLE("[1E10]", 1E10);
        TEST_DOUBLE("[1e10]", 1e10);
        TEST_DOUBLE("[1E+10]", 1E+10);
        TEST_DOUBLE("[1E-10]", 1E-10);
        TEST_DOUBLE("[-1E10]", -1E10);
        TEST_DOUBLE("[-1e10]", -1e10);
        TEST_DOUBLE("[-1E+10]", -1E+10);
        TEST_DOUBLE("[-1E-10]", -1E-10);
        TEST_DOUBLE("[1.234E+10]", 1.234E+10);
        TEST_DOUBLE("[1.234E-10]", 1.234E-10);
        TEST_DOUBLE("[1.79769e+308]", 1.79769e+308);
        TEST_DOUBLE("[2.22507e-308]", 2.22507e-308);
        TEST_DOUBLE("[-1.79769e+308]", -1.79769e+308);
        TEST_DOUBLE("[-2.22507e-308]", -2.22507e-308);
        TEST_DOUBLE("[4.9406564584124654e-324]", 4.9406564584124654e-324); // minimum denormal
        TEST_DOUBLE("[2.2250738585072009e-308]", 2.2250738585072009e-308); // Max subnormal double
        TEST_DOUBLE("[2.2250738585072014e-308]", 2.2250738585072014e-308); // Min normal positive double
        TEST_DOUBLE("[1.7976931348623157e+308]", 1.7976931348623157e+308); // Max double
        TEST_DOUBLE("[1e-10000]", 0.0);                                   // must underflow
        TEST_DOUBLE("[18446744073709551616]",
                    18446744073709551616.0);    // 2^64 (max of uint64_t + 1, force to use double)
        TEST_DOUBLE("[-9223372036854775809]",
                    -9223372036854775809.0);    // -2^63 - 1(min of int64_t + 1, force to use double)
        TEST_DOUBLE("[0.9868011474609375]",
                    0.9868011474609375);          // https://github.com/miloyip/rapidjson/issues/120
        TEST_DOUBLE("[123e34]", 123e34);                                  // Fast Path Cases In Disguise
        TEST_DOUBLE("[45913141877270640000.0]", 45913141877270640000.0);
        TEST_DOUBLE("[2.2250738585072011e-308]",
                    2.2250738585072011e-308);
        //TEST_DOUBLE("[1e-00011111111111]", 0.0);
        //TEST_DOUBLE("[-1e-00011111111111]", -0.0);
        TEST_DOUBLE("[1e-214748363]", 0.0);
        TEST_DOUBLE("[1e-214748364]", 0.0);
        //TEST_DOUBLE("[1e-21474836311]", 0.0);
        TEST_DOUBLE("[0.017976931348623157e+310]", 1.7976931348623157e+308); // Max double in another form

        // Since
        // abs((2^-1022 - 2^-1074) - 2.2250738585072012e-308) = 3.109754131239141401123495768877590405345064751974375599... ¡Á 10^-324
        // abs((2^-1022) - 2.2250738585072012e-308) = 1.830902327173324040642192159804623318305533274168872044... ¡Á 10 ^ -324
        // So 2.2250738585072012e-308 should round to 2^-1022 = 2.2250738585072014e-308
        TEST_DOUBLE("[2.2250738585072012e-308]",
                    2.2250738585072014e-308);

        // More closer to normal/subnormal boundary
        // boundary = 2^-1022 - 2^-1075 = 2.225073858507201136057409796709131975934819546351645648... ¡Á 10^-308
        TEST_DOUBLE("[2.22507385850720113605740979670913197593481954635164564e-308]",
                    2.2250738585072009e-308);
        TEST_DOUBLE("[2.22507385850720113605740979670913197593481954635164565e-308]",
                    2.2250738585072014e-308);

        // 1.0 is in (1.0 - 2^-54, 1.0 + 2^-53)
        // 1.0 - 2^-54 = 0.999999999999999944488848768742172978818416595458984375
        TEST_DOUBLE("[0.999999999999999944488848768742172978818416595458984375]", 1.0); // round to even
        TEST_DOUBLE("[0.999999999999999944488848768742172978818416595458984374]",
                    0.99999999999999989); // previous double
        TEST_DOUBLE("[0.999999999999999944488848768742172978818416595458984376]", 1.0); // next double
        // 1.0 + 2^-53 = 1.00000000000000011102230246251565404236316680908203125
        TEST_DOUBLE("[1.00000000000000011102230246251565404236316680908203125]", 1.0); // round to even
        TEST_DOUBLE("[1.00000000000000011102230246251565404236316680908203124]", 1.0); // previous double
        TEST_DOUBLE("[1.00000000000000011102230246251565404236316680908203126]",
                    1.00000000000000022); // next double

        // Numbers from https://github.com/floitsch/double-conversion/blob/master/test/cctest/test-strtod.cc

        TEST_DOUBLE("[72057594037927928.0]", 72057594037927928.0);
        TEST_DOUBLE("[72057594037927936.0]", 72057594037927936.0);
        TEST_DOUBLE("[72057594037927932.0]", 72057594037927936.0);
        TEST_DOUBLE("[7205759403792793199999e-5]", 72057594037927928.0);
        TEST_DOUBLE("[7205759403792793200001e-5]", 72057594037927936.0);

        TEST_DOUBLE("[9223372036854774784.0]", 9223372036854774784.0);
        TEST_DOUBLE("[9223372036854775808.0]", 9223372036854775808.0);
        TEST_DOUBLE("[9223372036854775296.0]", 9223372036854775808.0);
        TEST_DOUBLE("[922337203685477529599999e-5]", 9223372036854774784.0);
        TEST_DOUBLE("[922337203685477529600001e-5]", 9223372036854775808.0);

        TEST_DOUBLE("[10141204801825834086073718800384]", 10141204801825834086073718800384.0);
        TEST_DOUBLE("[10141204801825835211973625643008]", 10141204801825835211973625643008.0);
        TEST_DOUBLE("[10141204801825834649023672221696]", 10141204801825835211973625643008.0);
        TEST_DOUBLE("[1014120480182583464902367222169599999e-5]", 10141204801825834086073718800384.0);
        TEST_DOUBLE("[1014120480182583464902367222169600001e-5]", 10141204801825835211973625643008.0);

        TEST_DOUBLE("[5708990770823838890407843763683279797179383808]",
                    5708990770823838890407843763683279797179383808.0);
        TEST_DOUBLE("[5708990770823839524233143877797980545530986496]",
                    5708990770823839524233143877797980545530986496.0);
        TEST_DOUBLE("[5708990770823839207320493820740630171355185152]",
                    5708990770823839524233143877797980545530986496.0);
        TEST_DOUBLE("[5708990770823839207320493820740630171355185151999e-3]",
                    5708990770823838890407843763683279797179383808.0);
        TEST_DOUBLE("[5708990770823839207320493820740630171355185152001e-3]",
                    5708990770823839524233143877797980545530986496.0);

        {
            char n1e308[312];   // '1' followed by 308 '0'
            n1e308[0] = '[';
            n1e308[1] = '1';
            for (int j = 2; j < 310; j++)
            {
                n1e308[j] = '0';
            }
            n1e308[310] = ']';
            n1e308[311] = '\0';
            TEST_DOUBLE(n1e308, 1E308);
        }

        // Cover trimming
        TEST_DOUBLE(
            "[2.22507385850720113605740979670913197593481954635164564802342610972482222202107694551652952390813508"
            "7914149158913039621106870086438694594645527657207407820621743379988141063267329253552286881372149012"
            "9811224514518898490572223072852551331557550159143974763979834118019993239625482890171070818506906306"
            "6665599493827577257201576306269066333264756530000924588831643303777979186961204949739037782970490505"
            "1080609940730262937128958950003583799967207254304360284078895771796150945516748243471030702609144621"
            "5722898802581825451803257070188608721131280795122334262883686223215037756666225039825343359745688844"
            "2390026549819838548794829220689472168983109969836584681402285424333066033985088644580400103493397042"
            "7567186443383770486037861622771738545623065874679014086723327636718751234567890123456789012345678901"
            "e-308]",
            2.2250738585072014e-308);
    }

    SECTION("strings")
    {
        auto TEST_STRING = [](const std::string & json_string, const std::string & expected)
        {
            CAPTURE(json_string);
            CAPTURE(expected);
            CHECK(json::parse(json_string)[0].get<std::string>() == expected);
        };

        TEST_STRING("[\"\"]", "");
        TEST_STRING("[\"Hello\"]", "Hello");
        TEST_STRING("[\"Hello\\nWorld\"]", "Hello\nWorld");
        //TEST_STRING("[\"Hello\\u0000World\"]", "Hello\0World");
        TEST_STRING("[\"\\\"\\\\/\\b\\f\\n\\r\\t\"]", "\"\\/\b\f\n\r\t");
        TEST_STRING("[\"\\u0024\"]", "\x24");         // Dollar sign U+0024
        TEST_STRING("[\"\\u00A2\"]", "\xC2\xA2");     // Cents sign U+00A2
        TEST_STRING("[\"\\u20AC\"]", "\xE2\x82\xAC"); // Euro sign U+20AC
        TEST_STRING("[\"\\uD834\\uDD1E\"]", "\xF0\x9D\x84\x9E");  // G clef sign U+1D11E
    }

    SECTION("roundtrip")
    {
        // test cases are from https://github.com/miloyip/nativejson-benchmark/tree/master/test/data/roundtrip

        for (auto filename :
                {
                    "test/data/json_roundtrip/roundtrip01.json",
                    "test/data/json_roundtrip/roundtrip02.json",
                    "test/data/json_roundtrip/roundtrip03.json",
                    "test/data/json_roundtrip/roundtrip04.json",
                    "test/data/json_roundtrip/roundtrip05.json",
                    "test/data/json_roundtrip/roundtrip06.json",
                    "test/data/json_roundtrip/roundtrip07.json",
                    "test/data/json_roundtrip/roundtrip08.json",
                    "test/data/json_roundtrip/roundtrip09.json",
                    "test/data/json_roundtrip/roundtrip10.json",
                    "test/data/json_roundtrip/roundtrip11.json",
                    "test/data/json_roundtrip/roundtrip12.json",
                    "test/data/json_roundtrip/roundtrip13.json",
                    "test/data/json_roundtrip/roundtrip14.json",
                    "test/data/json_roundtrip/roundtrip15.json",
                    "test/data/json_roundtrip/roundtrip16.json",
                    "test/data/json_roundtrip/roundtrip17.json",
                    "test/data/json_roundtrip/roundtrip18.json",
                    "test/data/json_roundtrip/roundtrip19.json",
                    "test/data/json_roundtrip/roundtrip20.json",
                    "test/data/json_roundtrip/roundtrip21.json",
                    "test/data/json_roundtrip/roundtrip22.json",
                    "test/data/json_roundtrip/roundtrip23.json",
                    //"test/data/json_roundtrip/roundtrip24.json", // roundtrip error
                    //"test/data/json_roundtrip/roundtrip25.json", // roundtrip error
                    //"test/data/json_roundtrip/roundtrip26.json", // roundtrip error
                    //"test/data/json_roundtrip/roundtrip27.json", // roundtrip error
                    //"test/data/json_roundtrip/roundtrip28.json", // roundtrip error
                    "test/data/json_roundtrip/roundtrip29.json",
                    //"test/data/json_roundtrip/roundtrip30.json", // roundtrip error
                    //"test/data/json_roundtrip/roundtrip31.json", // roundtrip error
                    "test/data/json_roundtrip/roundtrip32.json"
                })
        {
            CAPTURE(filename);
            std::ifstream f(filename);
            std::string json_string( (std::istreambuf_iterator<char>(f) ),
                                     (std::istreambuf_iterator<char>()) );

            json j = json::parse(json_string);
            CHECK(j.dump() == json_string);
        }
    }
}

TEST_CASE("test suite from json-test-suite")
{
    SECTION("read all sample.json")
    {
        // read a file with all unicode characters stored as single-character
        // strings in a JSON array
        std::ifstream f("test/data/json_testsuite/sample.json");
        json j;
        CHECK_NOTHROW(j << f);

        // the array has 3 elements
        CHECK(j.size() == 3);
    }
}

TEST_CASE("json.org examples")
{
    // here, we list all JSON values from http://json.org/example

    SECTION("1.json")
    {
        std::ifstream f("test/data/json.org/1.json");
        json j;
        CHECK_NOTHROW(j << f);
    }

    SECTION("2.json")
    {
        std::ifstream f("test/data/json.org/2.json");
        json j;
        CHECK_NOTHROW(j << f);
    }

    SECTION("3.json")
    {
        std::ifstream f("test/data/json.org/3.json");
        json j;
        CHECK_NOTHROW(j << f);
    }

    SECTION("4.json")
    {
        std::ifstream f("test/data/json.org/4.json");
        json j;
        CHECK_NOTHROW(j << f);
    }

    SECTION("5.json")
    {
        std::ifstream f("test/data/json.org/5.json");
        json j;
        CHECK_NOTHROW(j << f);
    }
}

TEST_CASE("RFC 7159 examples")
{
    // here, we list all JSON values from the RFC 7159 document

    SECTION("7. Strings")
    {
        CHECK(json::parse("\"\\u005C\"") == json("\\"));
        CHECK(json::parse("\"\\uD834\\uDD1E\"") == json("𝄞"));
        CHECK(json::parse("\"𝄞\"") == json("𝄞"));
    }

    SECTION("8.3 String Comparison")
    {
        CHECK(json::parse("\"a\\b\"") == json::parse("\"a\u005Cb\""));
    }

    SECTION("13 Examples")
    {
        {
            CHECK_NOTHROW(json(R"(
            {
                 "Image": {
                     "Width":  800,
                     "Height": 600,
                     "Title":  "View from 15th Floor",
                     "Thumbnail": {
                         "Url":    "http://www.example.com/image/481989943",
                         "Height": 125,
                         "Width":  100
                     },
                     "Animated" : false,
                     "IDs": [116, 943, 234, 38793]
                   }
               }
            )"));
        }

        {
            CHECK_NOTHROW(json(R"(
                [
                    {
                       "precision": "zip",
                       "Latitude":  37.7668,
                       "Longitude": -122.3959,
                       "Address":   "",
                       "City":      "SAN FRANCISCO",
                       "State":     "CA",
                       "Zip":       "94107",
                       "Country":   "US"
                    },
                    {
                       "precision": "zip",
                       "Latitude":  37.371991,
                       "Longitude": -122.026020,
                       "Address":   "",
                       "City":      "SUNNYVALE",
                       "State":     "CA",
                       "Zip":       "94085",
                       "Country":   "US"
                    }
            ])"));
        }

        CHECK(json::parse("\"Hello world!\"") == json("Hello world!"));
        CHECK(json::parse("42") == json(42));
        CHECK(json::parse("true") == json(true));
    }
}

TEST_CASE("Unicode", "[hide]")
{
    SECTION("full enumeration of Unicode codepoints")
    {
        // create a string from a codepoint
        auto codepoint_to_unicode = [](std::size_t cp)
        {
            char* buffer = new char[10];
            sprintf(buffer, "\\u%04lx", cp);
            std::string result(buffer);
            delete[] buffer;
            return result;
        };

        // generate all codepoints
        for (std::size_t cp = 0; cp <= 0x10FFFFu; ++cp)
        {
            // The Unicode standard permanently reserves these code point
            // values for UTF-16 encoding of the high and low surrogates, and
            // they will never be assigned a character, so there should be no
            // reason to encode them. The official Unicode standard says that
            // no UTF forms, including UTF-16, can encode these code points.
            if (cp >= 0xD800u and cp <= 0xDFFFu)
            {
                continue;
            }

            std::string res;

            if (cp < 0x10000u)
            {
                // codepoint can be represented with 16 bit
                res += codepoint_to_unicode(cp);
            }
            else
            {
                // codepoint can be represented with a pair
                res += codepoint_to_unicode(0xd800u + (((cp - 0x10000u) >> 10) & 0x3ffu));
                res += codepoint_to_unicode(0xdc00u + ((cp - 0x10000u) & 0x3ffu));
            }

            try
            {
                json j1, j2;
                CHECK_NOTHROW(j1 = json::parse("\"" + res + "\""));
                CHECK_NOTHROW(j2 = json::parse(j1.dump()));
                CHECK(j1 == j2);
            }
            catch (std::invalid_argument)
            {
                // we ignore parsing errors
            }
        }
    }

    SECTION("read all unicode characters")
    {
        // read a file with all unicode characters stored as single-character
        // strings in a JSON array
        std::ifstream f("test/data/json_nlohmann_tests/all_unicode.json");
        json j;
        CHECK_NOTHROW(j << f);

        // the array has 1112064 + 1 elemnts (a terminating "null" value)
        CHECK(j.size() == 1112065);

        SECTION("check JSON Pointers")
        {
            for (auto s : j)
            {
                // skip non-string JSON values
                if (not s.is_string())
                {
                    continue;
                }

                std::string ptr = s;

                // tilde must be followed by 0 or 1
                if (ptr == "~")
                {
                    ptr += "0";
                }

                // JSON Pointers must begin with "/"
                ptr = "/" + ptr;

                CHECK_NOTHROW(json::json_pointer("/" + ptr));

                // check escape/unescape roundtrip
                auto escaped = json::json_pointer::escape(ptr);
                json::json_pointer::unescape(escaped);
                CHECK(escaped == ptr);
            }
        }
    }

    SECTION("ignore byte-order-mark")
    {
        // read a file with a UTF-8 BOM
        std::ifstream f("test/data/json_nlohmann_tests/bom.json");
        json j;
        CHECK_NOTHROW(j << f);
    }

    SECTION("error for incomplete/wrong BOM")
    {
        CHECK_THROWS_AS(json::parse("\xef\xbb"), std::invalid_argument);
        CHECK_THROWS_AS(json::parse("\xef\xbb\xbb"), std::invalid_argument);
    }
}

TEST_CASE("JSON pointers")
{
    SECTION("errors")
    {
        CHECK_THROWS_AS(json::json_pointer("foo"), std::domain_error);
        CHECK_THROWS_WITH(json::json_pointer("foo"), "JSON pointer must be empty or begin with '/'");

        CHECK_THROWS_AS(json::json_pointer("/~~"), std::domain_error);
        CHECK_THROWS_WITH(json::json_pointer("/~~"), "escape error: '~' must be followed with '0' or '1'");

        CHECK_THROWS_AS(json::json_pointer("/~"), std::domain_error);
        CHECK_THROWS_WITH(json::json_pointer("/~"), "escape error: '~' must be followed with '0' or '1'");

        json::json_pointer p;
        CHECK_THROWS_AS(p.top(), std::domain_error);
        CHECK_THROWS_WITH(p.top(), "JSON pointer has no parent");
        CHECK_THROWS_AS(p.pop_back(), std::domain_error);
        CHECK_THROWS_WITH(p.pop_back(), "JSON pointer has no parent");
    }

    SECTION("examples from RFC 6901")
    {
        SECTION("nonconst access")
        {
            json j = R"(
            {
                "foo": ["bar", "baz"],
                "": 0,
                "a/b": 1,
                "c%d": 2,
                "e^f": 3,
                "g|h": 4,
                "i\\j": 5,
                "k\"l": 6,
                " ": 7,
                "m~n": 8
            }
            )"_json;

            // the whole document
            CHECK(j[json::json_pointer()] == j);
            CHECK(j[json::json_pointer("")] == j);

            // array access
            CHECK(j[json::json_pointer("/foo")] == j["foo"]);
            CHECK(j[json::json_pointer("/foo/0")] == j["foo"][0]);
            CHECK(j[json::json_pointer("/foo/1")] == j["foo"][1]);
            CHECK(j["/foo/1"_json_pointer] == j["foo"][1]);

            // checked array access
            CHECK(j.at(json::json_pointer("/foo/0")) == j["foo"][0]);
            CHECK(j.at(json::json_pointer("/foo/1")) == j["foo"][1]);

            // empty string access
            CHECK(j[json::json_pointer("/")] == j[""]);

            // other cases
            CHECK(j[json::json_pointer("/ ")] == j[" "]);
            CHECK(j[json::json_pointer("/c%d")] == j["c%d"]);
            CHECK(j[json::json_pointer("/e^f")] == j["e^f"]);
            CHECK(j[json::json_pointer("/g|h")] == j["g|h"]);
            CHECK(j[json::json_pointer("/i\\j")] == j["i\\j"]);
            CHECK(j[json::json_pointer("/k\"l")] == j["k\"l"]);

            // checked access
            CHECK(j.at(json::json_pointer("/ ")) == j[" "]);
            CHECK(j.at(json::json_pointer("/c%d")) == j["c%d"]);
            CHECK(j.at(json::json_pointer("/e^f")) == j["e^f"]);
            CHECK(j.at(json::json_pointer("/g|h")) == j["g|h"]);
            CHECK(j.at(json::json_pointer("/i\\j")) == j["i\\j"]);
            CHECK(j.at(json::json_pointer("/k\"l")) == j["k\"l"]);

            // escaped access
            CHECK(j[json::json_pointer("/a~1b")] == j["a/b"]);
            CHECK(j[json::json_pointer("/m~0n")] == j["m~n"]);

            // unescaped access
            CHECK_THROWS_AS(j[json::json_pointer("/a/b")], std::out_of_range);
            CHECK_THROWS_WITH(j[json::json_pointer("/a/b")], "unresolved reference token 'b'");
            // "/a/b" works for JSON {"a": {"b": 42}}
            CHECK(json({{"a", {{"b", 42}}}})[json::json_pointer("/a/b")] == json(42));

            // unresolved access
            json j_primitive = 1;
            CHECK_THROWS_AS(j_primitive["/foo"_json_pointer], std::out_of_range);
            CHECK_THROWS_WITH(j_primitive["/foo"_json_pointer], "unresolved reference token 'foo'");
            CHECK_THROWS_AS(j_primitive.at("/foo"_json_pointer), std::out_of_range);
            CHECK_THROWS_WITH(j_primitive.at("/foo"_json_pointer), "unresolved reference token 'foo'");
        }

        SECTION("const access")
        {
            const json j = R"(
            {
                "foo": ["bar", "baz"],
                "": 0,
                "a/b": 1,
                "c%d": 2,
                "e^f": 3,
                "g|h": 4,
                "i\\j": 5,
                "k\"l": 6,
                " ": 7,
                "m~n": 8
            }
            )"_json;

            // the whole document
            CHECK(j[json::json_pointer()] == j);
            CHECK(j[json::json_pointer("")] == j);

            // array access
            CHECK(j[json::json_pointer("/foo")] == j["foo"]);
            CHECK(j[json::json_pointer("/foo/0")] == j["foo"][0]);
            CHECK(j[json::json_pointer("/foo/1")] == j["foo"][1]);
            CHECK(j["/foo/1"_json_pointer] == j["foo"][1]);

            // checked array access
            CHECK(j.at(json::json_pointer("/foo/0")) == j["foo"][0]);
            CHECK(j.at(json::json_pointer("/foo/1")) == j["foo"][1]);

            // empty string access
            CHECK(j[json::json_pointer("/")] == j[""]);

            // other cases
            CHECK(j[json::json_pointer("/ ")] == j[" "]);
            CHECK(j[json::json_pointer("/c%d")] == j["c%d"]);
            CHECK(j[json::json_pointer("/e^f")] == j["e^f"]);
            CHECK(j[json::json_pointer("/g|h")] == j["g|h"]);
            CHECK(j[json::json_pointer("/i\\j")] == j["i\\j"]);
            CHECK(j[json::json_pointer("/k\"l")] == j["k\"l"]);

            // checked access
            CHECK(j.at(json::json_pointer("/ ")) == j[" "]);
            CHECK(j.at(json::json_pointer("/c%d")) == j["c%d"]);
            CHECK(j.at(json::json_pointer("/e^f")) == j["e^f"]);
            CHECK(j.at(json::json_pointer("/g|h")) == j["g|h"]);
            CHECK(j.at(json::json_pointer("/i\\j")) == j["i\\j"]);
            CHECK(j.at(json::json_pointer("/k\"l")) == j["k\"l"]);

            // escaped access
            CHECK(j[json::json_pointer("/a~1b")] == j["a/b"]);
            CHECK(j[json::json_pointer("/m~0n")] == j["m~n"]);

            // unescaped access
            CHECK_THROWS_AS(j.at(json::json_pointer("/a/b")), std::out_of_range);
            CHECK_THROWS_WITH(j.at(json::json_pointer("/a/b")), "key 'a' not found");

            // unresolved access
            const json j_primitive = 1;
            CHECK_THROWS_AS(j_primitive["/foo"_json_pointer], std::out_of_range);
            CHECK_THROWS_WITH(j_primitive["/foo"_json_pointer], "unresolved reference token 'foo'");
            CHECK_THROWS_AS(j_primitive.at("/foo"_json_pointer), std::out_of_range);
            CHECK_THROWS_WITH(j_primitive.at("/foo"_json_pointer), "unresolved reference token 'foo'");
        }

        SECTION("user-defined string literal")
        {
            json j = R"(
            {
                "foo": ["bar", "baz"],
                "": 0,
                "a/b": 1,
                "c%d": 2,
                "e^f": 3,
                "g|h": 4,
                "i\\j": 5,
                "k\"l": 6,
                " ": 7,
                "m~n": 8
            }
            )"_json;

            // the whole document
            CHECK(j[""_json_pointer] == j);

            // array access
            CHECK(j["/foo"_json_pointer] == j["foo"]);
            CHECK(j["/foo/0"_json_pointer] == j["foo"][0]);
            CHECK(j["/foo/1"_json_pointer] == j["foo"][1]);
        }
    }

    SECTION("array access")
    {
        SECTION("nonconst access")
        {
            json j = {1, 2, 3};
            const json j_const = j;

            // check reading access
            CHECK(j["/0"_json_pointer] == j[0]);
            CHECK(j["/1"_json_pointer] == j[1]);
            CHECK(j["/2"_json_pointer] == j[2]);

            // assign to existing index
            j["/1"_json_pointer] = 13;
            CHECK(j[1] == json(13));

            // assign to nonexisting index
            j["/3"_json_pointer] = 33;
            CHECK(j[3] == json(33));

            // assign to nonexisting index (with gap)
            j["/5"_json_pointer] = 55;
            CHECK(j == json({1, 13, 3, 33, nullptr, 55}));

            // error with leading 0
            CHECK_THROWS_AS(j["/01"_json_pointer], std::domain_error);
            CHECK_THROWS_WITH(j["/01"_json_pointer], "array index must not begin with '0'");
            CHECK_THROWS_AS(j_const["/01"_json_pointer], std::domain_error);
            CHECK_THROWS_WITH(j_const["/01"_json_pointer], "array index must not begin with '0'");
            CHECK_THROWS_AS(j.at("/01"_json_pointer), std::domain_error);
            CHECK_THROWS_WITH(j.at("/01"_json_pointer), "array index must not begin with '0'");
            CHECK_THROWS_AS(j_const.at("/01"_json_pointer), std::domain_error);
            CHECK_THROWS_WITH(j_const.at("/01"_json_pointer), "array index must not begin with '0'");

            // error with incorrect numbers
            CHECK_THROWS_AS(j["/one"_json_pointer] = 1, std::invalid_argument);

            // assign to "-"
            j["/-"_json_pointer] = 99;
            CHECK(j == json({1, 13, 3, 33, nullptr, 55, 99}));

            // error when using "-" in const object
            CHECK_THROWS_AS(j_const["/-"_json_pointer], std::out_of_range);
            CHECK_THROWS_WITH(j_const["/-"_json_pointer], "array index '-' (3) is out of range");

            // error when using "-" with at
            CHECK_THROWS_AS(j.at("/-"_json_pointer), std::out_of_range);
            CHECK_THROWS_WITH(j.at("/-"_json_pointer), "array index '-' (7) is out of range");
            CHECK_THROWS_AS(j_const.at("/-"_json_pointer), std::out_of_range);
            CHECK_THROWS_WITH(j_const.at("/-"_json_pointer), "array index '-' (3) is out of range");
        }

        SECTION("const access")
        {
            const json j = {1, 2, 3};

            // check reading access
            CHECK(j["/0"_json_pointer] == j[0]);
            CHECK(j["/1"_json_pointer] == j[1]);
            CHECK(j["/2"_json_pointer] == j[2]);

            // assign to nonexisting index
            CHECK_THROWS_AS(j.at("/3"_json_pointer), std::out_of_range);
            CHECK_THROWS_WITH(j.at("/3"_json_pointer), "array index 3 is out of range");

            // assign to nonexisting index (with gap)
            CHECK_THROWS_AS(j.at("/5"_json_pointer), std::out_of_range);
            CHECK_THROWS_WITH(j.at("/5"_json_pointer), "array index 5 is out of range");

            // assign to "-"
            CHECK_THROWS_AS(j["/-"_json_pointer], std::out_of_range);
            CHECK_THROWS_WITH(j["/-"_json_pointer], "array index '-' (3) is out of range");
            CHECK_THROWS_AS(j.at("/-"_json_pointer), std::out_of_range);
            CHECK_THROWS_WITH(j.at("/-"_json_pointer), "array index '-' (3) is out of range");
        }

    }

    SECTION("flatten")
    {
        json j =
        {
            {"pi", 3.141},
            {"happy", true},
            {"name", "Niels"},
            {"nothing", nullptr},
            {
                "answer", {
                    {"everything", 42}
                }
            },
            {"list", {1, 0, 2}},
            {
                "object", {
                    {"currency", "USD"},
                    {"value", 42.99},
                    {"", "empty string"},
                    {"/", "slash"},
                    {"~", "tilde"},
                    {"~1", "tilde1"}
                }
            }
        };

        json j_flatten =
        {
            {"/pi", 3.141},
            {"/happy", true},
            {"/name", "Niels"},
            {"/nothing", nullptr},
            {"/answer/everything", 42},
            {"/list/0", 1},
            {"/list/1", 0},
            {"/list/2", 2},
            {"/object/currency", "USD"},
            {"/object/value", 42.99},
            {"/object/", "empty string"},
            {"/object/~1", "slash"},
            {"/object/~0", "tilde"},
            {"/object/~01", "tilde1"}
        };

        // check if flattened result is as expected
        CHECK(j.flatten() == j_flatten);

        // check if unflattened result is as expected
        CHECK(j_flatten.unflatten() == j);

        // error for nonobjects
        CHECK_THROWS_AS(json(1).unflatten(), std::domain_error);
        CHECK_THROWS_WITH(json(1).unflatten(), "only objects can be unflattened");

        // error for nonprimitve values
        CHECK_THROWS_AS(json({{"/1", {1, 2, 3}}}).unflatten(), std::domain_error);
        CHECK_THROWS_WITH(json({{"/1", {1, 2, 3}}}).unflatten(), "values in object must be primitive");

        // error for conflicting values
        json j_error = {{"", 42}, {"/foo", 17}};
        CHECK_THROWS_AS(j_error.unflatten(), std::domain_error);
        CHECK_THROWS_WITH(j_error.unflatten(), "invalid value to unflatten");

        // explicit roundtrip check
        CHECK(j.flatten().unflatten() == j);

        // roundtrip for primitive values
        json j_null;
        CHECK(j_null.flatten().unflatten() == j_null);
        json j_number = 42;
        CHECK(j_number.flatten().unflatten() == j_number);
        json j_boolean = false;
        CHECK(j_boolean.flatten().unflatten() == j_boolean);
        json j_string = "foo";
        CHECK(j_string.flatten().unflatten() == j_string);

        // roundtrip for empty structured values (will be unflattened to null)
        json j_array(json::value_t::array);
        CHECK(j_array.flatten().unflatten() == json());
        json j_object(json::value_t::object);
        CHECK(j_object.flatten().unflatten() == json());
    }

    SECTION("string representation")
    {
        for (auto ptr :
                {"", "/foo", "/foo/0", "/", "/a~1b", "/c%d", "/e^f", "/g|h", "/i\\j", "/k\"l", "/ ", "/m~0n"
                })
        {
            CHECK(json::json_pointer(ptr).to_string() == ptr);
        }
    }
}

TEST_CASE("JSON patch")
{
    SECTION("examples from RFC 6902")
    {
        SECTION("4. Operations")
        {
            // the ordering of members in JSON objects is not significant:
            json op1 = R"({ "op": "add", "path": "/a/b/c", "value": "foo" })"_json;
            json op2 = R"({ "path": "/a/b/c", "op": "add", "value": "foo" })"_json;
            json op3 = R"({ "value": "foo", "path": "/a/b/c", "op": "add" })"_json;

            // check if the operation objects are equivalent
            CHECK(op1 == op2);
            CHECK(op1 == op3);
        }

        SECTION("4.1 add")
        {
            json patch = R"([{ "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] }])"_json;

            // However, the object itself or an array containing it does need
            // to exist, and it remains an error for that not to be the case.
            // For example, an "add" with a target location of "/a/b" starting
            // with this document
            json doc1 = R"({ "a": { "foo": 1 } })"_json;

            // is not an error, because "a" exists, and "b" will be added to
            // its value.
            CHECK_NOTHROW(doc1.patch(patch));
            CHECK(doc1.patch(patch) == R"(
                {
                    "a": {
                        "foo": 1,
                        "b": {
                            "c": [ "foo", "bar" ]
                        }
                    }
                }
            )"_json);

            // It is an error in this document:
            json doc2 = R"({ "q": { "bar": 2 } })"_json;

            // because "a" does not exist.
            CHECK_THROWS_AS(doc2.patch(patch), std::out_of_range);
            CHECK_THROWS_WITH(doc2.patch(patch), "key 'a' not found");
        }

        SECTION("4.2 remove")
        {
            // If removing an element from an array, any elements above the
            // specified index are shifted one position to the left.
            json doc = {1, 2, 3, 4};
            json patch = {{{"op", "remove"}, {"path", "/1"}}};
            CHECK(doc.patch(patch) == json({1, 3, 4}));
        }

        SECTION("A.1. Adding an Object Member")
        {
            // An example target JSON document:
            json doc = R"(
                    { "foo": "bar"}
                )"_json;

            // A JSON Patch document:
            json patch = R"(
                    [
                        { "op": "add", "path": "/baz", "value": "qux" }
                    ]
                )"_json;

            // The resulting JSON document:
            json expected = R"(
                    {
                        "baz": "qux",
                        "foo": "bar"
                    }
                )"_json;

            // check if patched value is as expected
            CHECK(doc.patch(patch) == expected);

            // check roundtrip
            CHECK(doc.patch(json::diff(doc, expected)) == expected);
        }

        SECTION("A.2. Adding an Array Element")
        {
            // An example target JSON document:
            json doc = R"(
                    { "foo": [ "bar", "baz" ] }
                )"_json;

            // A JSON Patch document:
            json patch = R"(
                    [
                        { "op": "add", "path": "/foo/1", "value": "qux" }
                    ]
                )"_json;

            // The resulting JSON document:
            json expected = R"(
                    { "foo": [ "bar", "qux", "baz" ] }
                )"_json;

            // check if patched value is as expected
            CHECK(doc.patch(patch) == expected);

            // check roundtrip
            CHECK(doc.patch(json::diff(doc, expected)) == expected);
        }

        SECTION("A.3. Removing an Object Member")
        {
            // An example target JSON document:
            json doc = R"(
                    {
                        "baz": "qux",
                        "foo": "bar"
                    }
                )"_json;

            // A JSON Patch document:
            json patch = R"(
                    [
                        { "op": "remove", "path": "/baz" }
                    ]
                )"_json;

            // The resulting JSON document:
            json expected = R"(
                    { "foo": "bar" }
                )"_json;

            // check if patched value is as expected
            CHECK(doc.patch(patch) == expected);

            // check roundtrip
            CHECK(doc.patch(json::diff(doc, expected)) == expected);
        }

        SECTION("A.4. Removing an Array Element")
        {
            // An example target JSON document:
            json doc = R"(
                    { "foo": [ "bar", "qux", "baz" ] }
                )"_json;

            // A JSON Patch document:
            json patch = R"(
                    [
                        { "op": "remove", "path": "/foo/1" }
                    ]
                )"_json;

            // The resulting JSON document:
            json expected = R"(
                    { "foo": [ "bar", "baz" ] }
                )"_json;

            // check if patched value is as expected
            CHECK(doc.patch(patch) == expected);

            // check roundtrip
            CHECK(doc.patch(json::diff(doc, expected)) == expected);
        }

        SECTION("A.5. Replacing a Value")
        {
            // An example target JSON document:
            json doc = R"(
                    {
                        "baz": "qux",
                        "foo": "bar"
                    }
                )"_json;

            // A JSON Patch document:
            json patch = R"(
                    [
                        { "op": "replace", "path": "/baz", "value": "boo" }
                    ]
                )"_json;

            json expected = R"(
                    {
                        "baz": "boo",
                        "foo": "bar"
                    }
                )"_json;

            // check if patched value is as expected
            CHECK(doc.patch(patch) == expected);

            // check roundtrip
            CHECK(doc.patch(json::diff(doc, expected)) == expected);
        }

        SECTION("A.6. Moving a Value")
        {
            // An example target JSON document:
            json doc = R"(
                    {
                        "foo": {
                           "bar": "baz",
                            "waldo": "fred"
                        },
                        "qux": {
                            "corge": "grault"
                        }
                    }
                )"_json;

            // A JSON Patch document:
            json patch = R"(
                    [
                        { "op": "move", "from": "/foo/waldo", "path": "/qux/thud" }
                    ]
                )"_json;

            // The resulting JSON document:
            json expected = R"(
                    {
                        "foo": {
                           "bar": "baz"
                        },
                        "qux": {
                            "corge": "grault",
                            "thud": "fred"
                        }
                    }
                )"_json;

            // check if patched value is as expected
            CHECK(doc.patch(patch) == expected);

            // check roundtrip
            CHECK(doc.patch(json::diff(doc, expected)) == expected);
        }

        SECTION("A.7. Moving a Value")
        {
            // An example target JSON document:
            json doc = R"(
                    { "foo": [ "all", "grass", "cows", "eat" ] }
                )"_json;

            // A JSON Patch document:
            json patch = R"(
                    [
                        { "op": "move", "from": "/foo/1", "path": "/foo/3" }
                    ]
                )"_json;

            // The resulting JSON document:
            json expected = R"(
                    { "foo": [ "all", "cows", "eat", "grass" ] }
                )"_json;

            // check if patched value is as expected
            CHECK(doc.patch(patch) == expected);

            // check roundtrip
            CHECK(doc.patch(json::diff(doc, expected)) == expected);
        }

        SECTION("A.8. Testing a Value: Success")
        {
            // An example target JSON document:
            json doc = R"(
                    {
                         "baz": "qux",
                         "foo": [ "a", 2, "c" ]
                    }
                )"_json;

            // A JSON Patch document that will result in successful evaluation:
            json patch = R"(
                    [
                        { "op": "test", "path": "/baz", "value": "qux" },
                        { "op": "test", "path": "/foo/1", "value": 2 }
                    ]
                )"_json;

            // check if evaluation does not throw
            CHECK_NOTHROW(doc.patch(patch));
            // check if patched document is unchanged
            CHECK(doc.patch(patch) == doc);
        }

        SECTION("A.9. Testing a Value: Error")
        {
            // An example target JSON document:
            json doc = R"(
                    { "baz": "qux" }
                )"_json;

            // A JSON Patch document that will result in an error condition:
            json patch = R"(
                    [
                        { "op": "test", "path": "/baz", "value": "bar" }
                    ]
                )"_json;

            // check that evaluation throws
            CHECK_THROWS_AS(doc.patch(patch), std::domain_error);
            CHECK_THROWS_WITH(doc.patch(patch), "unsuccessful: " + patch[0].dump());
        }

        SECTION("A.10. Adding a Nested Member Object")
        {
            // An example target JSON document:
            json doc = R"(
                    { "foo": "bar" }
                )"_json;

            // A JSON Patch document:
            json patch = R"(
                    [
                        { "op": "add", "path": "/child", "value": { "grandchild": { } } }
                    ]
                )"_json;

            // The resulting JSON document:
            json expected = R"(
                {
                    "foo": "bar",
                    "child": {
                        "grandchild": {
                        }
                    }
                }
                )"_json;

            // check if patched value is as expected
            CHECK(doc.patch(patch) == expected);

            // check roundtrip
            CHECK(doc.patch(json::diff(doc, expected)) == expected);
        }

        SECTION("A.11. Ignoring Unrecognized Elements")
        {
            // An example target JSON document:
            json doc = R"(
                    { "foo": "bar" }
                )"_json;

            // A JSON Patch document:
            json patch = R"(
                    [
                        { "op": "add", "path": "/baz", "value": "qux", "xyz": 123 }
                    ]
                )"_json;

            json expected = R"(
                    {
                        "foo": "bar",
                        "baz": "qux"
                    } 
                )"_json;

            // check if patched value is as expected
            CHECK(doc.patch(patch) == expected);

            // check roundtrip
            CHECK(doc.patch(json::diff(doc, expected)) == expected);
        }

        SECTION("A.12. Adding to a Nonexistent Target")
        {
            // An example target JSON document:
            json doc = R"(
                    { "foo": "bar" }
                )"_json;

            // A JSON Patch document:
            json patch = R"(
                    [
                        { "op": "add", "path": "/baz/bat", "value": "qux" }
                    ]
                )"_json;

            // This JSON Patch document, applied to the target JSON document
            // above, would result in an error (therefore, it would not be
            // applied), because the "add" operation's target location that
            // references neither the root of the document, nor a member of
            // an existing object, nor a member of an existing array.

            CHECK_THROWS_AS(doc.patch(patch), std::out_of_range);
            CHECK_THROWS_WITH(doc.patch(patch), "key 'baz' not found");
        }

        // A.13. Invalid JSON Patch Document
        // not applicable

        SECTION("A.14. Escape Ordering")
        {
            // An example target JSON document:
            json doc = R"(
                    {
                        "/": 9,
                        "~1": 10
                    }
                )"_json;

            // A JSON Patch document:
            json patch = R"(
                    [
                        {"op": "test", "path": "/~01", "value": 10}
                    ]
                )"_json;

            json expected = R"(
                    {
                        "/": 9,
                        "~1": 10
                    } 
                )"_json;

            // check if patched value is as expected
            CHECK(doc.patch(patch) == expected);

            // check roundtrip
            CHECK(doc.patch(json::diff(doc, expected)) == expected);
        }

        SECTION("A.15. Comparing Strings and Numbers")
        {
            // An example target JSON document:
            json doc = R"(
                    {
                        "/": 9,
                        "~1": 10
                    } 
                )"_json;

            // A JSON Patch document that will result in an error condition:
            json patch = R"(
                    [
                        {"op": "test", "path": "/~01", "value": "10"}
                    ]
                )"_json;

            // check that evaluation throws
            CHECK_THROWS_AS(doc.patch(patch), std::domain_error);
            CHECK_THROWS_WITH(doc.patch(patch), "unsuccessful: " + patch[0].dump());
        }

        SECTION("A.16. Adding an Array Value")
        {
            // An example target JSON document:
            json doc = R"(
                    { "foo": ["bar"] }
                )"_json;

            // A JSON Patch document:
            json patch = R"(
                    [
                        { "op": "add", "path": "/foo/-", "value": ["abc", "def"] }
                    ]
                )"_json;

            // The resulting JSON document:
            json expected = R"(
                    { "foo": ["bar", ["abc", "def"]] }
                )"_json;

            // check if patched value is as expected
            CHECK(doc.patch(patch) == expected);

            // check roundtrip
            CHECK(doc.patch(json::diff(doc, expected)) == expected);
        }
    }

    SECTION("own examples")
    {
        SECTION("add")
        {
            SECTION("add to the root element")
            {
                // If the path is the root of the target document - the
                // specified value becomes the entire content of the target
                // document.

                // An example target JSON document:
                json doc = 17;

                // A JSON Patch document:
                json patch = R"(
                        [
                            { "op": "add", "path": "", "value": [1,2,3] }
                        ]
                    )"_json;

                // The resulting JSON document:
                json expected = {1, 2, 3};

                // check if patched value is as expected
                CHECK(doc.patch(patch) == expected);

                // check roundtrip
                CHECK(doc.patch(json::diff(doc, expected)) == expected);
            }

            SECTION("add to end of the array")
            {
                // The specified index MUST NOT be greater than the number of
                // elements in the array. The example below uses and index of
                // exactly the number of elements in the array which is legal.

                // An example target JSON document:
                json doc = {0, 1, 2};

                // A JSON Patch document:
                json patch = R"(
                    [
                        { "op": "add", "path": "/3", "value": 3 }
                    ]
                )"_json;

                // The resulting JSON document:
                json expected = {0, 1, 2, 3};

                // check if patched value is as expected
                CHECK(doc.patch(patch) == expected);

                // check roundtrip
                CHECK(doc.patch(json::diff(doc, expected)) == expected);
            }
        }

        SECTION("copy")
        {
            // An example target JSON document:
            json doc = R"(
                {
                    "foo": {
                        "bar": "baz",
                        "waldo": "fred"
                    },
                    "qux": {
                       "corge": "grault"
                    }
                }
            )"_json;

            // A JSON Patch document:
            json patch = R"(
                [
                    { "op": "copy", "from": "/foo/waldo", "path": "/qux/thud" }
                ]
            )"_json;

            // The resulting JSON document:
            json expected = R"(
                {
                    "foo": {
                        "bar": "baz",
                        "waldo": "fred"
                    },
                    "qux": {
                       "corge": "grault",
                       "thud": "fred"
                    }
                }
            )"_json;

            // check if patched value is as expected
            CHECK(doc.patch(patch) == expected);

            // check roundtrip
            CHECK(doc.patch(json::diff(doc, expected)) == expected);
        }

        SECTION("replace")
        {
            json j = "string";
            json patch = {{{"op", "replace"}, {"path", ""}, {"value", 1}}};
            CHECK(j.patch(patch) == json(1));
        }

        SECTION("documentation GIF")
        {
            {
                // a JSON patch
                json p1 = R"(
                     [{"op": "add", "path": "/GB", "value": "London"}]
                    )"_json;

                // a JSON value
                json source = R"(
                      {"D": "Berlin", "F": "Paris"}
                    )"_json;

                // apply the patch
                json target = source.patch(p1);
                // target = { "D": "Berlin", "F": "Paris", "GB": "London" }
                CHECK(target == R"({ "D": "Berlin", "F": "Paris", "GB": "London" })"_json);

                // create a diff from two JSONs
                json p2 = json::diff(target, source);
                // p2 = [{"op": "delete", "path": "/GB"}]
                CHECK(p2 == R"([{"op":"remove","path":"/GB"}])"_json);
            }
            {
                // a JSON value
                json j = {"good", "bad", "ugly"};

                // a JSON pointer
                auto ptr = json::json_pointer("/2");

                // use to access elements
                j[ptr] = {{"it", "cattivo"}};
                CHECK(j == R"(["good","bad",{"it":"cattivo"}])"_json);

                // use user-defined string literal
                j["/2/en"_json_pointer] = "ugly";
                CHECK(j == R"(["good","bad",{"en":"ugly","it":"cattivo"}])"_json);

                json flat = j.flatten();
                CHECK(flat == R"({"/0":"good","/1":"bad","/2/en":"ugly","/2/it":"cattivo"})"_json);
            }
        }
    }

    SECTION("errors")
    {
        SECTION("unknown operation")
        {
            SECTION("not an array")
            {
                json j;
                json patch = {{"op", "add"}, {"path", ""}, {"value", 1}};
                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
                CHECK_THROWS_WITH(j.patch(patch), "JSON patch must be an array of objects");
            }

            SECTION("not an array of objects")
            {
                json j;
                json patch = {"op", "add", "path", "", "value", 1};
                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
                CHECK_THROWS_WITH(j.patch(patch), "JSON patch must be an array of objects");
            }

            SECTION("missing 'op'")
            {
                json j;
                json patch = {{{"foo", "bar"}}};
                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
                CHECK_THROWS_WITH(j.patch(patch), "operation must have member 'op'");
            }

            SECTION("non-string 'op'")
            {
                json j;
                json patch = {{{"op", 1}}};
                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
                CHECK_THROWS_WITH(j.patch(patch), "operation must have string member 'op'");
            }

            SECTION("invalid operation")
            {
                json j;
                json patch = {{{"op", "foo"}, {"path", ""}}};
                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
                CHECK_THROWS_WITH(j.patch(patch), "operation value 'foo' is invalid");
            }
        }

        SECTION("add")
        {
            SECTION("missing 'path'")
            {
                json j;
                json patch = {{{"op", "add"}}};
                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
                CHECK_THROWS_WITH(j.patch(patch), "operation 'add' must have member 'path'");
            }

            SECTION("non-string 'path'")
            {
                json j;
                json patch = {{{"op", "add"}, {"path", 1}}};
                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
                CHECK_THROWS_WITH(j.patch(patch), "operation 'add' must have string member 'path'");
            }

            SECTION("missing 'value'")
            {
                json j;
                json patch = {{{"op", "add"}, {"path", ""}}};
                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
                CHECK_THROWS_WITH(j.patch(patch), "operation 'add' must have member 'value'");
            }

            SECTION("invalid array index")
            {
                json j = {1, 2};
                json patch = {{{"op", "add"}, {"path", "/4"}, {"value", 4}}};
                CHECK_THROWS_AS(j.patch(patch), std::out_of_range);
                CHECK_THROWS_WITH(j.patch(patch), "array index 4 is out of range");
            }
        }

        SECTION("remove")
        {
            SECTION("missing 'path'")
            {
                json j;
                json patch = {{{"op", "remove"}}};
                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
                CHECK_THROWS_WITH(j.patch(patch), "operation 'remove' must have member 'path'");
            }

            SECTION("non-string 'path'")
            {
                json j;
                json patch = {{{"op", "remove"}, {"path", 1}}};
                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
                CHECK_THROWS_WITH(j.patch(patch), "operation 'remove' must have string member 'path'");
            }

            SECTION("nonexisting target location (array)")
            {
                json j = {1, 2, 3};
                json patch = {{{"op", "remove"}, {"path", "/17"}}};
                CHECK_THROWS_AS(j.patch(patch), std::out_of_range);
                CHECK_THROWS_WITH(j.patch(patch), "array index 17 is out of range");
            }

            SECTION("nonexisting target location (object)")
            {
                json j = {{"foo", 1}, {"bar", 2}};
                json patch = {{{"op", "remove"}, {"path", "/baz"}}};
                CHECK_THROWS_AS(j.patch(patch), std::out_of_range);
                CHECK_THROWS_WITH(j.patch(patch), "key 'baz' not found");
            }

            SECTION("root element as target location")
            {
                json j = "string";
                json patch = {{{"op", "remove"}, {"path", ""}}};
                CHECK_THROWS_AS(j.patch(patch), std::domain_error);
                CHECK_THROWS_WITH(j.patch(patch), "JSON pointer has no parent");
            }
        }

        SECTION("replace")
        {
            SECTION("missing 'path'")
            {
                json j;
                json patch = {{{"op", "replace"}}};
                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
                CHECK_THROWS_WITH(j.patch(patch), "operation 'replace' must have member 'path'");
            }

            SECTION("non-string 'path'")
            {
                json j;
                json patch = {{{"op", "replace"}, {"path", 1}}};
                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
                CHECK_THROWS_WITH(j.patch(patch), "operation 'replace' must have string member 'path'");
            }

            SECTION("missing 'value'")
            {
                json j;
                json patch = {{{"op", "replace"}, {"path", ""}}};
                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
                CHECK_THROWS_WITH(j.patch(patch), "operation 'replace' must have member 'value'");
            }

            SECTION("nonexisting target location (array)")
            {
                json j = {1, 2, 3};
                json patch = {{{"op", "replace"}, {"path", "/17"}, {"value", 19}}};
                CHECK_THROWS_AS(j.patch(patch), std::out_of_range);
                CHECK_THROWS_WITH(j.patch(patch), "array index 17 is out of range");
            }

            SECTION("nonexisting target location (object)")
            {
                json j = {{"foo", 1}, {"bar", 2}};
                json patch = {{{"op", "replace"}, {"path", "/baz"}, {"value", 3}}};
                CHECK_THROWS_AS(j.patch(patch), std::out_of_range);
                CHECK_THROWS_WITH(j.patch(patch), "key 'baz' not found");
            }
        }

        SECTION("move")
        {
            SECTION("missing 'path'")
            {
                json j;
                json patch = {{{"op", "move"}}};
                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
                CHECK_THROWS_WITH(j.patch(patch), "operation 'move' must have member 'path'");
            }

            SECTION("non-string 'path'")
            {
                json j;
                json patch = {{{"op", "move"}, {"path", 1}}};
                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
                CHECK_THROWS_WITH(j.patch(patch), "operation 'move' must have string member 'path'");
            }

            SECTION("missing 'from'")
            {
                json j;
                json patch = {{{"op", "move"}, {"path", ""}}};
                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
                CHECK_THROWS_WITH(j.patch(patch), "operation 'move' must have member 'from'");
            }

            SECTION("non-string 'from'")
            {
                json j;
                json patch = {{{"op", "move"}, {"path", ""}, {"from", 1}}};
                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
                CHECK_THROWS_WITH(j.patch(patch), "operation 'move' must have string member 'from'");
            }

            SECTION("nonexisting from location (array)")
            {
                json j = {1, 2, 3};
                json patch = {{{"op", "move"}, {"path", "/0"}, {"from", "/5"}}};
                CHECK_THROWS_AS(j.patch(patch), std::out_of_range);
                CHECK_THROWS_WITH(j.patch(patch), "array index 5 is out of range");
            }

            SECTION("nonexisting from location (object)")
            {
                json j = {{"foo", 1}, {"bar", 2}};
                json patch = {{{"op", "move"}, {"path", "/baz"}, {"from", "/baz"}}};
                CHECK_THROWS_AS(j.patch(patch), std::out_of_range);
                CHECK_THROWS_WITH(j.patch(patch), "key 'baz' not found");
            }
        }

        SECTION("copy")
        {
            SECTION("missing 'path'")
            {
                json j;
                json patch = {{{"op", "copy"}}};
                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
                CHECK_THROWS_WITH(j.patch(patch), "operation 'copy' must have member 'path'");
            }

            SECTION("non-string 'path'")
            {
                json j;
                json patch = {{{"op", "copy"}, {"path", 1}}};
                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
                CHECK_THROWS_WITH(j.patch(patch), "operation 'copy' must have string member 'path'");
            }

            SECTION("missing 'from'")
            {
                json j;
                json patch = {{{"op", "copy"}, {"path", ""}}};
                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
                CHECK_THROWS_WITH(j.patch(patch), "operation 'copy' must have member 'from'");
            }

            SECTION("non-string 'from'")
            {
                json j;
                json patch = {{{"op", "copy"}, {"path", ""}, {"from", 1}}};
                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
                CHECK_THROWS_WITH(j.patch(patch), "operation 'copy' must have string member 'from'");
            }

            SECTION("nonexisting from location (array)")
            {
                json j = {1, 2, 3};
                json patch = {{{"op", "copy"}, {"path", "/0"}, {"from", "/5"}}};
                CHECK_THROWS_AS(j.patch(patch), std::out_of_range);
                CHECK_THROWS_WITH(j.patch(patch), "array index 5 is out of range");
            }

            SECTION("nonexisting from location (object)")
            {
                json j = {{"foo", 1}, {"bar", 2}};
                json patch = {{{"op", "copy"}, {"path", "/fob"}, {"from", "/baz"}}};
                CHECK_THROWS_AS(j.patch(patch), std::out_of_range);
                CHECK_THROWS_WITH(j.patch(patch), "key 'baz' not found");
            }
        }

        SECTION("test")
        {
            SECTION("missing 'path'")
            {
                json j;
                json patch = {{{"op", "test"}}};
                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
                CHECK_THROWS_WITH(j.patch(patch), "operation 'test' must have member 'path'");
            }

            SECTION("non-string 'path'")
            {
                json j;
                json patch = {{{"op", "test"}, {"path", 1}}};
                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
                CHECK_THROWS_WITH(j.patch(patch), "operation 'test' must have string member 'path'");
            }

            SECTION("missing 'value'")
            {
                json j;
                json patch = {{{"op", "test"}, {"path", ""}}};
                CHECK_THROWS_AS(j.patch(patch), std::invalid_argument);
                CHECK_THROWS_WITH(j.patch(patch), "operation 'test' must have member 'value'");
            }
        }
    }

    SECTION("Examples from jsonpatch.com")
    {
        SECTION("Simple Example")
        {
            // The original document
            json doc = R"(
                {
                  "baz": "qux",
                  "foo": "bar"
                }
            )"_json;

            // The patch
            json patch = R"(
                [
                  { "op": "replace", "path": "/baz", "value": "boo" },
                  { "op": "add", "path": "/hello", "value": ["world"] },
                  { "op": "remove", "path": "/foo"}
                ]
            )"_json;

            // The result
            json result = R"(
                {
                   "baz": "boo",
                   "hello": ["world"]
                }
            )"_json;

            // check if patched value is as expected
            CHECK(doc.patch(patch) == result);

            // check roundtrip
            CHECK(doc.patch(json::diff(doc, result)) == result);
        }

        SECTION("Operations")
        {
            // The original document
            json doc = R"(
                {
                  "biscuits": [
                    {"name":"Digestive"},
                    {"name": "Choco Liebniz"}
                  ]
                }
            )"_json;

            SECTION("add")
            {
                // The patch
                json patch = R"(
                    [
                        {"op": "add", "path": "/biscuits/1", "value": {"name": "Ginger Nut"}}
                    ]
                )"_json;

                // The result
                json result = R"(
                    {
                      "biscuits": [
                        {"name": "Digestive"},
                        {"name": "Ginger Nut"},
                        {"name": "Choco Liebniz"}
                      ]
                    }
                )"_json;

                // check if patched value is as expected
                CHECK(doc.patch(patch) == result);

                // check roundtrip
                CHECK(doc.patch(json::diff(doc, result)) == result);
            }

            SECTION("remove")
            {
                // The patch
                json patch = R"(
                    [
                        {"op": "remove", "path": "/biscuits"}
                    ]
                )"_json;

                // The result
                json result = R"(
                    {}
                )"_json;

                // check if patched value is as expected
                CHECK(doc.patch(patch) == result);

                // check roundtrip
                CHECK(doc.patch(json::diff(doc, result)) == result);
            }

            SECTION("replace")
            {
                // The patch
                json patch = R"(
                    [
                        {"op": "replace", "path": "/biscuits/0/name", "value": "Chocolate Digestive"}
                    ]
                )"_json;

                // The result
                json result = R"(
                    {
                      "biscuits": [
                        {"name": "Chocolate Digestive"},
                        {"name": "Choco Liebniz"}
                      ]
                    }
                )"_json;

                // check if patched value is as expected
                CHECK(doc.patch(patch) == result);

                // check roundtrip
                CHECK(doc.patch(json::diff(doc, result)) == result);
            }

            SECTION("copy")
            {
                // The patch
                json patch = R"(
                    [
                        {"op": "copy", "from": "/biscuits/0", "path": "/best_biscuit"}
                    ]
                )"_json;

                // The result
                json result = R"(
                    {
                      "biscuits": [
                        {"name": "Digestive"},
                        {"name": "Choco Liebniz"}
                      ],
                      "best_biscuit": {
                        "name": "Digestive"
                      }
                    }
                )"_json;

                // check if patched value is as expected
                CHECK(doc.patch(patch) == result);

                // check roundtrip
                CHECK(doc.patch(json::diff(doc, result)) == result);
            }

            SECTION("move")
            {
                // The patch
                json patch = R"(
                    [
                        {"op": "move", "from": "/biscuits", "path": "/cookies"}
                    ]
                )"_json;

                // The result
                json result = R"(
                    {
                      "cookies": [
                        {"name": "Digestive"},
                        {"name": "Choco Liebniz"}
                      ]
                    }
                )"_json;

                // check if patched value is as expected
                CHECK(doc.patch(patch) == result);

                // check roundtrip
                CHECK(doc.patch(json::diff(doc, result)) == result);
            }

            SECTION("test")
            {
                // The patch
                json patch = R"(
                    [
                        {"op": "test", "path": "/best_biscuit/name", "value": "Choco Liebniz"}
                    ]
                )"_json;

                // the test will fail
                CHECK_THROWS_AS(doc.patch(patch), std::domain_error);
                CHECK_THROWS_WITH(doc.patch(patch), "unsuccessful: " + patch[0].dump());
            }
        }
    }

    SECTION("Examples from bruth.github.io/jsonpatch-js")
    {
        SECTION("add")
        {
            CHECK(R"( {} )"_json.patch(
                      R"( [{"op": "add", "path": "/foo", "value": "bar"}] )"_json
                  ) == R"( {"foo": "bar"} )"_json);

            CHECK(R"( {"foo": [1, 3]} )"_json.patch(
                      R"( [{"op": "add", "path": "/foo", "value": "bar"}] )"_json
                  ) == R"( {"foo": "bar"} )"_json);

            CHECK(R"( {"foo": [{}]} )"_json.patch(
                      R"( [{"op": "add", "path": "/foo/0/bar", "value": "baz"}] )"_json
                  ) == R"( {"foo": [{"bar": "baz"}]} )"_json);
        }

        SECTION("remove")
        {
            CHECK(R"( {"foo": "bar"} )"_json.patch(
                      R"( [{"op": "remove", "path": "/foo"}] )"_json
                  ) == R"( {} )"_json);

            CHECK(R"( {"foo": [1, 2, 3]} )"_json.patch(
                      R"( [{"op": "remove", "path": "/foo/1"}] )"_json
                  ) == R"( {"foo": [1, 3]} )"_json);

            CHECK(R"( {"foo": [{"bar": "baz"}]} )"_json.patch(
                      R"( [{"op": "remove", "path": "/foo/0/bar"}] )"_json
                  ) == R"( {"foo": [{}]} )"_json);
        }

        SECTION("replace")
        {
            CHECK(R"( {"foo": "bar"} )"_json.patch(
                      R"( [{"op": "replace", "path": "/foo", "value": 1}] )"_json
                  ) == R"( {"foo": 1} )"_json);

            CHECK(R"( {"foo": [1, 2, 3]} )"_json.patch(
                      R"( [{"op": "replace", "path": "/foo/1", "value": 4}] )"_json
                  ) == R"( {"foo": [1, 4, 3]} )"_json);

            CHECK(R"( {"foo": [{"bar": "baz"}]} )"_json.patch(
                      R"( [{"op": "replace", "path": "/foo/0/bar", "value": 1}] )"_json
                  ) == R"( {"foo": [{"bar": 1}]} )"_json);
        }

        SECTION("move")
        {
            CHECK(R"( {"foo": [1, 2, 3]} )"_json.patch(
                      R"( [{"op": "move", "from": "/foo", "path": "/bar"}] )"_json
                  ) == R"( {"bar": [1, 2, 3]} )"_json);
        }

        SECTION("copy")
        {
            CHECK(R"( {"foo": [1, 2, 3]} )"_json.patch(
                      R"( [{"op": "copy", "from": "/foo/1", "path": "/bar"}] )"_json
                  ) == R"( {"foo": [1, 2, 3], "bar": 2} )"_json);
        }

        SECTION("copy")
        {
            CHECK_NOTHROW(R"( {"foo": "bar"} )"_json.patch(
                              R"( [{"op": "test", "path": "/foo", "value": "bar"}] )"_json));
        }
    }
}

TEST_CASE("regression tests")
{
    SECTION("issue #60 - Double quotation mark is not parsed correctly")
    {
        SECTION("escape_dobulequote")
        {
            auto s = "[\"\\\"foo\\\"\"]";
            json j = json::parse(s);
            auto expected = R"(["\"foo\""])"_json;
            CHECK(j == expected);
        }
    }

    SECTION("issue #70 - Handle infinity and NaN cases")
    {
        SECTION("NAN value")
        {
            CHECK(json(NAN) == json());
            CHECK(json(json::number_float_t(NAN)) == json());
        }

        SECTION("infinity")
        {
            CHECK(json(INFINITY) == json());
            CHECK(json(json::number_float_t(INFINITY)) == json());
        }
    }

    SECTION("pull request #71 - handle enum type")
    {
        enum { t = 0 };
        json j = json::array();
        j.push_back(t);

        j.push_back(json::object(
        {
            {"game_type", t}
        }));
    }

    SECTION("issue #76 - dump() / parse() not idempotent")
    {
        // create JSON object
        json fields;
        fields["one"] = std::string("one");
        fields["two"] = std::string("two three");
        fields["three"] = std::string("three \"four\"");

        // create another JSON object by deserializing the serialization
        std::string payload = fields.dump();
        json parsed_fields = json::parse(payload);

        // check individual fields to match both objects
        CHECK(parsed_fields["one"] == fields["one"]);
        CHECK(parsed_fields["two"] == fields["two"]);
        CHECK(parsed_fields["three"] == fields["three"]);

        // check individual fields to match original input
        CHECK(parsed_fields["one"] == std::string("one"));
        CHECK(parsed_fields["two"] == std::string("two three"));
        CHECK(parsed_fields["three"] == std::string("three \"four\""));

        // check equality of the objects
        CHECK(parsed_fields == fields);

        // check equality of the serialized objects
        CHECK(fields.dump() == parsed_fields.dump());

        // check everything in one line
        CHECK(fields == json::parse(fields.dump()));
    }

    SECTION("issue #82 - lexer::get_number return NAN")
    {
        const auto content = R"(
        {
            "Test":"Test1",
            "Number":100,
            "Foo":42.42
        })";

        std::stringstream ss;
        ss << content;
        json j;
        ss >> j;

        std::string test = j["Test"];
        CHECK(test == "Test1");
        int number = j["Number"];
        CHECK(number == 100);
        float foo = j["Foo"];
        CHECK(foo == Approx(42.42));
    }

    SECTION("issue #89 - nonstandard integer type")
    {
        // create JSON class with nonstandard integer number type
        using custom_json =
            nlohmann::basic_json<std::map, std::vector, std::string, bool, int32_t, uint32_t, float>;
        custom_json j;
        j["int_1"] = 1;
        // we need to cast to int to compile with Catch - the value is int32_t
        CHECK(static_cast<int>(j["int_1"]) == 1);

        // tests for correct handling of non-standard integers that overflow the type selected by the user

        // unsigned integer object creation - expected to wrap and still be stored as an integer
        j = 4294967296U; // 2^32
        CHECK(static_cast<int>(j.type()) == static_cast<int>(custom_json::value_t::number_unsigned));
        CHECK(j.get<uint32_t>() == 0);  // Wrap

        // unsigned integer parsing - expected to overflow and be stored as a float
        j = custom_json::parse("4294967296"); // 2^32
        CHECK(static_cast<int>(j.type()) == static_cast<int>(custom_json::value_t::number_float));
        CHECK(j.get<float>() == 4294967296.0f);

        // integer object creation - expected to wrap and still be stored as an integer
        j = -2147483649LL; // -2^31-1
        CHECK(static_cast<int>(j.type()) == static_cast<int>(custom_json::value_t::number_integer));
        CHECK(j.get<int32_t>() == 2147483647);  // Wrap

        // integer parsing - expected to overflow and be stored as a float with rounding
        j = custom_json::parse("-2147483649"); // -2^31
        CHECK(static_cast<int>(j.type()) == static_cast<int>(custom_json::value_t::number_float));
        CHECK(j.get<float>() == -2147483650.0f);
    }

    SECTION("issue #93 reverse_iterator operator inheritance problem")
    {
        {
            json a = {1, 2, 3};
            json::reverse_iterator rit = a.rbegin();
            ++rit;
            CHECK(*rit == json(2));
            CHECK(rit.value() == json(2));
        }
        {
            json a = {1, 2, 3};
            json::reverse_iterator rit = ++a.rbegin();
        }
        {
            json a = {1, 2, 3};
            json::reverse_iterator rit = a.rbegin();
            ++rit;
            json b = {0, 0, 0};
            std::transform(rit, a.rend(), b.rbegin(), [](json el)
            {
                return el;
            });
            CHECK(b == json({0, 1, 2}));
        }
        {
            json a = {1, 2, 3};
            json b = {0, 0, 0};
            std::transform(++a.rbegin(), a.rend(), b.rbegin(), [](json el)
            {
                return el;
            });
            CHECK(b == json({0, 1, 2}));
        }
    }

    SECTION("issue #100 - failed to iterator json object with reverse_iterator")
    {
        json config =
        {
            { "111", 111 },
            { "112", 112 },
            { "113", 113 }
        };

        std::stringstream ss;

        for (auto it = config.begin(); it != config.end(); ++it)
        {
            ss << it.key() << ": " << it.value() << '\n';
        }

        for (auto it = config.rbegin(); it != config.rend(); ++it)
        {
            ss << it.key() << ": " << it.value() << '\n';
        }

        CHECK(ss.str() == "111: 111\n112: 112\n113: 113\n113: 113\n112: 112\n111: 111\n");
    }

    SECTION("issue #101 - binary string causes numbers to be dumped as hex")
    {
        int64_t number = 10;
        std::string bytes{"\x00" "asdf\n", 6};
        json j;
        j["int64"] = number;
        j["binary string"] = bytes;
        // make sure the number is really printed as decimal "10" and not as
        // hexadecimal "a"
        CHECK(j.dump() == "{\"binary string\":\"\\u0000asdf\\n\",\"int64\":10}");
    }

    SECTION("issue #111 - subsequent unicode chars")
    {
        std::string bytes{0x7, 0x7};
        json j;
        j["string"] = bytes;
        CHECK(j["string"] == "\u0007\u0007");
    }

    SECTION("issue #144 - implicit assignment to std::string fails")
    {
        json o = {{"name", "value"}};

        std::string s1 = o["name"];
        CHECK(s1 == "value");

        std::string s2;
        s2 = o["name"];

        CHECK(s2 == "value");
    }

    SECTION("issue #146 - character following a surrogate pair is skipped")
    {
        CHECK(json::parse("\"\\ud80c\\udc60abc\"").get<json::string_t>() == u8"\U00013060abc");
    }

    SECTION("issue #171 - Cannot index by key of type static constexpr const char*")
    {
        json j;

        // Non-const access with key as "char []"
        char array_key[] = "Key1";
        CHECK_NOTHROW(j[array_key] = 1);
        CHECK(j[array_key] == json(1));

        // Non-const access with key as "const char[]"
        const char const_array_key[] = "Key2";
        CHECK_NOTHROW(j[const_array_key] = 2);
        CHECK(j[const_array_key] == json(2));

        // Non-const access with key as "char *"
        char _ptr_key[] = "Key3";
        char* ptr_key = &_ptr_key[0];
        CHECK_NOTHROW(j[ptr_key] = 3);
        CHECK(j[ptr_key] == json(3));

        // Non-const access with key as "const char *"
        const char* const_ptr_key = "Key4";
        CHECK_NOTHROW(j[const_ptr_key] = 4);
        CHECK(j[const_ptr_key] == json(4));

        // Non-const access with key as "static constexpr const char *"
        static constexpr const char* constexpr_ptr_key = "Key5";
        CHECK_NOTHROW(j[constexpr_ptr_key] = 5);
        CHECK(j[constexpr_ptr_key] == json(5));

        const json j_const = j;

        // Const access with key as "char []"
        CHECK(j_const[array_key] == json(1));

        // Const access with key as "const char[]"
        CHECK(j_const[const_array_key] == json(2));

        // Const access with key as "char *"
        CHECK(j_const[ptr_key] == json(3));

        // Const access with key as "const char *"
        CHECK(j_const[const_ptr_key] == json(4));

        // Const access with key as "static constexpr const char *"
        CHECK(j_const[constexpr_ptr_key] == json(5));
    }

    SECTION("issue #186 miloyip/nativejson-benchmark: floating-point parsing")
    {
        json j;

        j = json::parse("-0.0");
        CHECK(j.get<double>() == -0.0);

        j = json::parse("2.22507385850720113605740979670913197593481954635164564e-308");
        CHECK(j.get<double>() == 2.2250738585072009e-308);

        j = json::parse("0.999999999999999944488848768742172978818416595458984374");
        CHECK(j.get<double>() == 0.99999999999999989);

        j = json::parse("1.00000000000000011102230246251565404236316680908203126");
        CHECK(j.get<double>() == 1.00000000000000022);

        j = json::parse("7205759403792793199999e-5");
        CHECK(j.get<double>() == 72057594037927928.0);

        j = json::parse("922337203685477529599999e-5");
        CHECK(j.get<double>() == 9223372036854774784.0);

        j = json::parse("1014120480182583464902367222169599999e-5");
        CHECK(j.get<double>() == 10141204801825834086073718800384.0);

        j = json::parse("5708990770823839207320493820740630171355185151999e-3");
        CHECK(j.get<double>() == 5708990770823838890407843763683279797179383808.0);

        // create JSON class with nonstandard float number type

        // float
        nlohmann::basic_json<std::map, std::vector, std::string, bool, int32_t, uint32_t, float> j_float =
            1.23e25f;
        CHECK(j_float.get<float>() == 1.23e25f);

        // double
        nlohmann::basic_json<std::map, std::vector, std::string, bool, int64_t, uint64_t, double> j_double =
            1.23e35f;
        CHECK(j_double.get<double>() == 1.23e35f);

        // long double
        nlohmann::basic_json<std::map, std::vector, std::string, bool, int64_t, uint64_t, long double>
        j_long_double = 1.23e45L;
        CHECK(j_long_double.get<long double>() == 1.23e45L);
    }

    SECTION("issue #228 - double values are serialized with commas as decimal points")
    {
        json j1a = 23.42;
        json j1b = json::parse("23.42");

        json j2a = 2342e-2;
        //issue #230
        //json j2b = json::parse("2342e-2");

        json j3a = 10E3;
        json j3b = json::parse("10E3");
        json j3c = json::parse("10e3");

        // class to create a locale that would use a comma for decimals
        class CommaDecimalSeparator : public std::numpunct<char>
        {
          protected:
            char do_decimal_point() const
            {
                return ',';
            }
        };

        // change locale to mess with decimal points
        std::locale::global(std::locale(std::locale(), new CommaDecimalSeparator));

        CHECK(j1a.dump() == "23.42");
        CHECK(j1b.dump() == "23.42");

        // check if locale is properly reset
        std::stringstream ss;
        ss.imbue(std::locale(std::locale(), new CommaDecimalSeparator));
        ss << 47.11;
        CHECK(ss.str() == "47,11");
        ss << j1a;
        CHECK(ss.str() == "47,1123.42");
        ss << 47.11;
        CHECK(ss.str() == "47,1123.4247,11");

        CHECK(j2a.dump() == "23.42");
        //issue #230
        //CHECK(j2b.dump() == "23.42");

        CHECK(j3a.dump() == "10000");
        CHECK(j3b.dump() == "10000");
        CHECK(j3c.dump() == "10000");
        //CHECK(j3b.dump() == "1E04"); // roundtrip error
        //CHECK(j3c.dump() == "1e04"); // roundtrip error
    }

    SECTION("issue #233 - Can't use basic_json::iterator as a base iterator for std::move_iterator")
    {
        json source = {"a", "b", "c"};
        json expected = {"a", "b"};
        json dest;

        std::copy_n(std::make_move_iterator(source.begin()), 2, std::back_inserter(dest));

        CHECK(dest == expected);
    }

    SECTION("issue #235 - ambiguous overload for 'push_back' and 'operator+='")
    {
        json data = {{"key", "value"}};
        data.push_back({"key2", "value2"});
        data += {"key3", "value3"};

        CHECK(data == json({{"key", "value"}, {"key2", "value2"}, {"key3", "value3"}}));
    }

    SECTION("issue #269 - diff generates incorrect patch when removing multiple array elements")
    {
        json doc = R"( { "arr1": [1, 2, 3, 4] } )"_json;
        json expected = R"( { "arr1": [1, 2] } )"_json;

        // check roundtrip
        CHECK(doc.patch(json::diff(doc, expected)) == expected);
    }
}

// special test case to check if memory is leaked if constructor throws

template<class T>
struct my_allocator : std::allocator<T>
{
    template<class... Args>
    void construct(T*, Args&& ...)
    {
        throw std::bad_alloc();
    }
};

TEST_CASE("bad_alloc")
{
    SECTION("bad_alloc")
    {
        // create JSON type using the throwing allocator
        using my_json = nlohmann::basic_json<std::map,
              std::vector,
              std::string,
              bool,
              std::int64_t,
              std::uint64_t,
              double,
              my_allocator>;

        // creating an object should throw
        CHECK_THROWS_AS(my_json j(my_json::value_t::object), std::bad_alloc);
    }
}
