/*
    __ _____ _____ _____
 __|  |   __|     |   | |  JSON for Modern C++ (test suite)
|  |  |__   |  |  | | | |  version 3.10.5
|_____|_____|_____|_|___|  https://github.com/nlohmann/json

Licensed under the MIT License <http://opensource.org/licenses/MIT>.
SPDX-License-Identifier: MIT
Copyright (c) 2013-2022 Niels Lohmann <http://nlohmann.me>.

Permission is hereby  granted, free of charge, to any  person obtaining a copy
of this software and associated  documentation files (the "Software"), to deal
in the Software  without restriction, including without  limitation the rights
to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell
copies  of  the Software,  and  to  permit persons  to  whom  the Software  is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE  IS PROVIDED "AS  IS", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR
IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,
FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE
AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

#include "doctest_compatibility.h"

#include <nlohmann/json.hpp>

TEST_CASE_TEMPLATE("element access 2", Json, nlohmann::json, nlohmann::ordered_json)
{
    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::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::object());
                CHECK(j_const.at("array") == Json({1, 2, 3}));

#ifdef JSON_HAS_CPP_17
                CHECK(j.at(std::string_view("integer")) == Json(1));
                CHECK(j.at(std::string_view("unsigned")) == Json(1u));
                CHECK(j.at(std::string_view("boolean")) == Json(true));
                CHECK(j.at(std::string_view("null")) == Json(nullptr));
                CHECK(j.at(std::string_view("string")) == Json("hello world"));
                CHECK(j.at(std::string_view("floating")) == Json(42.23));
                CHECK(j.at(std::string_view("object")) == Json::object());
                CHECK(j.at(std::string_view("array")) == Json({1, 2, 3}));

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

            SECTION("access outside bounds")
            {
                CHECK_THROWS_WITH_AS(j.at("foo"), "[json.exception.out_of_range.403] key 'foo' not found", typename Json::out_of_range&);
                CHECK_THROWS_WITH_AS(j_const.at("foo"), "[json.exception.out_of_range.403] key 'foo' not found", typename Json::out_of_range&);


#ifdef JSON_HAS_CPP_17
                CHECK_THROWS_WITH_AS(j.at(std::string_view("foo")), "[json.exception.out_of_range.403] key 'foo' not found", typename Json::out_of_range&);
                CHECK_THROWS_WITH_AS(j_const.at(std::string_view("foo")), "[json.exception.out_of_range.403] key 'foo' not found", typename Json::out_of_range&);
#endif
            }

            SECTION("access on non-object type")
            {
                SECTION("null")
                {
                    Json j_nonobject(Json::value_t::null);
                    const Json j_nonobject_const(j_nonobject);
                    CHECK_THROWS_WITH_AS(j_nonobject.at("foo"), "[json.exception.type_error.304] cannot use at() with null", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_nonobject_const.at("foo"), "[json.exception.type_error.304] cannot use at() with null", typename Json::type_error&);

#ifdef JSON_HAS_CPP_17
                    CHECK_THROWS_WITH_AS(j_nonobject.at(std::string_view(std::string_view("foo"))), "[json.exception.type_error.304] cannot use at() with null", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_nonobject_const.at(std::string_view(std::string_view("foo"))), "[json.exception.type_error.304] cannot use at() with null", typename Json::type_error&);
#endif
                }

                SECTION("boolean")
                {
                    Json j_nonobject(Json::value_t::boolean);
                    const Json j_nonobject_const(j_nonobject);
                    CHECK_THROWS_WITH_AS(j_nonobject.at("foo"), "[json.exception.type_error.304] cannot use at() with boolean", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_nonobject_const.at("foo"), "[json.exception.type_error.304] cannot use at() with boolean", typename Json::type_error&);

#ifdef JSON_HAS_CPP_17
                    CHECK_THROWS_WITH_AS(j_nonobject.at(std::string_view("foo")), "[json.exception.type_error.304] cannot use at() with boolean", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_nonobject_const.at(std::string_view("foo")), "[json.exception.type_error.304] cannot use at() with boolean", typename Json::type_error&);
#endif
                }

                SECTION("string")
                {
                    Json j_nonobject(Json::value_t::string);
                    const Json j_nonobject_const(j_nonobject);
                    CHECK_THROWS_WITH_AS(j_nonobject.at("foo"), "[json.exception.type_error.304] cannot use at() with string", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_nonobject_const.at("foo"), "[json.exception.type_error.304] cannot use at() with string", typename Json::type_error&);

#ifdef JSON_HAS_CPP_17
                    CHECK_THROWS_WITH_AS(j_nonobject.at(std::string_view("foo")), "[json.exception.type_error.304] cannot use at() with string", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_nonobject_const.at(std::string_view("foo")), "[json.exception.type_error.304] cannot use at() with string", typename Json::type_error&);
#endif
                }

                SECTION("array")
                {
                    Json j_nonobject(Json::value_t::array);
                    const Json j_nonobject_const(j_nonobject);
                    CHECK_THROWS_WITH_AS(j_nonobject.at("foo"), "[json.exception.type_error.304] cannot use at() with array", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_nonobject_const.at("foo"), "[json.exception.type_error.304] cannot use at() with array", typename Json::type_error&);

#ifdef JSON_HAS_CPP_17
                    CHECK_THROWS_WITH_AS(j_nonobject.at(std::string_view("foo")), "[json.exception.type_error.304] cannot use at() with array", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_nonobject_const.at(std::string_view("foo")), "[json.exception.type_error.304] cannot use at() with array", typename Json::type_error&);
#endif
                }

                SECTION("number (integer)")
                {
                    Json j_nonobject(Json::value_t::number_integer);
                    const Json j_nonobject_const(j_nonobject);
                    CHECK_THROWS_WITH_AS(j_nonobject.at("foo"), "[json.exception.type_error.304] cannot use at() with number", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_nonobject_const.at("foo"), "[json.exception.type_error.304] cannot use at() with number", typename Json::type_error&);

#ifdef JSON_HAS_CPP_17
                    CHECK_THROWS_WITH_AS(j_nonobject.at(std::string_view("foo")), "[json.exception.type_error.304] cannot use at() with number", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_nonobject_const.at(std::string_view("foo")), "[json.exception.type_error.304] cannot use at() with number", typename Json::type_error&);
#endif
                }

                SECTION("number (unsigned)")
                {
                    Json j_nonobject(Json::value_t::number_unsigned);
                    const Json j_nonobject_const(j_nonobject);
                    CHECK_THROWS_WITH_AS(j_nonobject.at("foo"), "[json.exception.type_error.304] cannot use at() with number", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_nonobject_const.at("foo"), "[json.exception.type_error.304] cannot use at() with number", typename Json::type_error&);

#ifdef JSON_HAS_CPP_17
                    CHECK_THROWS_WITH_AS(j_nonobject.at(std::string_view("foo")), "[json.exception.type_error.304] cannot use at() with number", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_nonobject_const.at(std::string_view("foo")), "[json.exception.type_error.304] cannot use at() with number", typename Json::type_error&);
#endif
                }

                SECTION("number (floating-point)")
                {
                    Json j_nonobject(Json::value_t::number_float);
                    const Json j_nonobject_const(j_nonobject);
                    CHECK_THROWS_WITH_AS(j_nonobject.at("foo"), "[json.exception.type_error.304] cannot use at() with number", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_nonobject_const.at("foo"), "[json.exception.type_error.304] cannot use at() with number", typename Json::type_error&);

#ifdef JSON_HAS_CPP_17
                    CHECK_THROWS_WITH_AS(j_nonobject.at(std::string_view("foo")), "[json.exception.type_error.304] cannot use at() with number", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_nonobject_const.at(std::string_view("foo")), "[json.exception.type_error.304] cannot use at() with number", typename Json::type_error&);
#endif
                }
            }
        }

        SECTION("access specified element with default value")
        {
            SECTION("given a key")
            {
                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::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::object());
                    CHECK(j_const.value("array", Json({10, 100})) == Json({1, 2, 3}));

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

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

                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}));

#ifdef JSON_HAS_CPP_17
                    CHECK(j.value(std::string_view("_"), 2) == 2);
                    CHECK(j.value(std::string_view("_"), 2u) == 2u);
                    CHECK(j.value(std::string_view("_"), false) == false);
                    CHECK(j.value(std::string_view("_"), "bar") == "bar");
                    CHECK(j.value(std::string_view("_"), 12.34) == Approx(12.34));
                    CHECK(j.value(std::string_view("_"), Json({{"foo", "bar"}})) == Json({{"foo", "bar"}}));
                    CHECK(j.value(std::string_view("_"), Json({10, 100})) == Json({10, 100}));

                    CHECK(j_const.value(std::string_view("_"), 2) == 2);
                    CHECK(j_const.value(std::string_view("_"), 2u) == 2u);
                    CHECK(j_const.value(std::string_view("_"), false) == false);
                    CHECK(j_const.value(std::string_view("_"), "bar") == "bar");
                    CHECK(j_const.value(std::string_view("_"), 12.34) == Approx(12.34));
                    CHECK(j_const.value(std::string_view("_"), Json({{"foo", "bar"}})) == Json({{"foo", "bar"}}));
                    CHECK(j_const.value(std::string_view("_"), Json({10, 100})) == Json({10, 100}));
#endif
                }

                SECTION("access on non-object type")
                {
                    SECTION("null")
                    {
                        Json j_nonobject(Json::value_t::null);
                        const Json j_nonobject_const(Json::value_t::null);
                        CHECK_THROWS_WITH_AS(j_nonobject.value("foo", 1), "[json.exception.type_error.306] cannot use value() with null", typename Json::type_error&);
                        CHECK_THROWS_WITH_AS(j_nonobject_const.value("foo", 1), "[json.exception.type_error.306] cannot use value() with null", typename Json::type_error&);

#ifdef JSON_HAS_CPP_17
                        CHECK_THROWS_WITH_AS(j_nonobject.value(std::string_view("foo"), 1), "[json.exception.type_error.306] cannot use value() with null", typename Json::type_error&);
                        CHECK_THROWS_WITH_AS(j_nonobject_const.value(std::string_view("foo"), 1), "[json.exception.type_error.306] cannot use value() with null", typename Json::type_error&);
#endif
                    }

                    SECTION("boolean")
                    {
                        Json j_nonobject(Json::value_t::boolean);
                        const Json j_nonobject_const(Json::value_t::boolean);
                        CHECK_THROWS_WITH_AS(j_nonobject.value("foo", 1), "[json.exception.type_error.306] cannot use value() with boolean", typename Json::type_error&);
                        CHECK_THROWS_WITH_AS(j_nonobject_const.value("foo", 1), "[json.exception.type_error.306] cannot use value() with boolean", typename Json::type_error&);

#ifdef JSON_HAS_CPP_17
                        CHECK_THROWS_WITH_AS(j_nonobject.value(std::string_view("foo"), 1), "[json.exception.type_error.306] cannot use value() with boolean", typename Json::type_error&);
                        CHECK_THROWS_WITH_AS(j_nonobject_const.value(std::string_view("foo"), 1), "[json.exception.type_error.306] cannot use value() with boolean", typename Json::type_error&);
#endif
                    }

                    SECTION("string")
                    {
                        Json j_nonobject(Json::value_t::string);
                        const Json j_nonobject_const(Json::value_t::string);
                        CHECK_THROWS_WITH_AS(j_nonobject.value("foo", 1), "[json.exception.type_error.306] cannot use value() with string", typename Json::type_error&);
                        CHECK_THROWS_WITH_AS(j_nonobject_const.value("foo", 1), "[json.exception.type_error.306] cannot use value() with string", typename Json::type_error&);

#ifdef JSON_HAS_CPP_17
                        CHECK_THROWS_WITH_AS(j_nonobject.value(std::string_view("foo"), 1), "[json.exception.type_error.306] cannot use value() with string", typename Json::type_error&);
                        CHECK_THROWS_WITH_AS(j_nonobject_const.value(std::string_view("foo"), 1), "[json.exception.type_error.306] cannot use value() with string", typename Json::type_error&);
#endif
                    }

                    SECTION("array")
                    {
                        Json j_nonobject(Json::value_t::array);
                        const Json j_nonobject_const(Json::value_t::array);
                        CHECK_THROWS_WITH_AS(j_nonobject.value("foo", 1), "[json.exception.type_error.306] cannot use value() with array", typename Json::type_error&);
                        CHECK_THROWS_WITH_AS(j_nonobject_const.value("foo", 1), "[json.exception.type_error.306] cannot use value() with array", typename Json::type_error&);

#ifdef JSON_HAS_CPP_17
                        CHECK_THROWS_WITH_AS(j_nonobject.value(std::string_view("foo"), 1), "[json.exception.type_error.306] cannot use value() with array", typename Json::type_error&);
                        CHECK_THROWS_WITH_AS(j_nonobject_const.value(std::string_view("foo"), 1), "[json.exception.type_error.306] cannot use value() with array", typename Json::type_error&);
#endif
                    }

                    SECTION("number (integer)")
                    {
                        Json j_nonobject(Json::value_t::number_integer);
                        const Json j_nonobject_const(Json::value_t::number_integer);
                        CHECK_THROWS_WITH_AS(j_nonobject.value("foo", 1), "[json.exception.type_error.306] cannot use value() with number", typename Json::type_error&);
                        CHECK_THROWS_WITH_AS(j_nonobject_const.value("foo", 1), "[json.exception.type_error.306] cannot use value() with number", typename Json::type_error&);

#ifdef JSON_HAS_CPP_17
                        CHECK_THROWS_WITH_AS(j_nonobject.value(std::string_view("foo"), 1), "[json.exception.type_error.306] cannot use value() with number", typename Json::type_error&);
                        CHECK_THROWS_WITH_AS(j_nonobject_const.value(std::string_view("foo"), 1), "[json.exception.type_error.306] cannot use value() with number", typename Json::type_error&);
#endif
                    }

                    SECTION("number (unsigned)")
                    {
                        Json j_nonobject(Json::value_t::number_unsigned);
                        const Json j_nonobject_const(Json::value_t::number_unsigned);
                        CHECK_THROWS_WITH_AS(j_nonobject.value("foo", 1), "[json.exception.type_error.306] cannot use value() with number", typename Json::type_error&);
                        CHECK_THROWS_WITH_AS(j_nonobject_const.value("foo", 1), "[json.exception.type_error.306] cannot use value() with number", typename Json::type_error&);

#ifdef JSON_HAS_CPP_17
                        CHECK_THROWS_WITH_AS(j_nonobject.value(std::string_view("foo"), 1), "[json.exception.type_error.306] cannot use value() with number", typename Json::type_error&);
                        CHECK_THROWS_WITH_AS(j_nonobject_const.value(std::string_view("foo"), 1), "[json.exception.type_error.306] cannot use value() with number", typename Json::type_error&);
#endif
                    }

                    SECTION("number (floating-point)")
                    {
                        Json j_nonobject(Json::value_t::number_float);
                        const Json j_nonobject_const(Json::value_t::number_float);
                        CHECK_THROWS_WITH_AS(j_nonobject.value("foo", 1), "[json.exception.type_error.306] cannot use value() with number", typename Json::type_error&);
                        CHECK_THROWS_WITH_AS(j_nonobject_const.value("foo", 1), "[json.exception.type_error.306] cannot use value() with number", typename Json::type_error&);

#ifdef JSON_HAS_CPP_17
                        CHECK_THROWS_WITH_AS(j_nonobject.value(std::string_view("foo"), 1), "[json.exception.type_error.306] cannot use value() with number", typename Json::type_error&);
                        CHECK_THROWS_WITH_AS(j_nonobject_const.value(std::string_view("foo"), 1), "[json.exception.type_error.306] cannot use value() with number", typename Json::type_error&);
#endif
                    }
                }
            }

            SECTION("given a JSON pointer")
            {
                SECTION("access existing value")
                {
                    CHECK(j.value("/integer"_json_pointer, 2) == 1);
                    CHECK(j.value("/integer"_json_pointer, 1.0) == Approx(1));
                    CHECK(j.value("/unsigned"_json_pointer, 2) == 1u);
                    CHECK(j.value("/unsigned"_json_pointer, 1.0) == Approx(1u));
                    CHECK(j.value("/null"_json_pointer, Json(1)) == Json());
                    CHECK(j.value("/boolean"_json_pointer, false) == true);
                    CHECK(j.value("/string"_json_pointer, "bar") == "hello world");
                    CHECK(j.value("/string"_json_pointer, std::string("bar")) == "hello world");
                    CHECK(j.value("/floating"_json_pointer, 12.34) == Approx(42.23));
                    CHECK(j.value("/floating"_json_pointer, 12) == 42);
                    CHECK(j.value("/object"_json_pointer, Json({{"foo", "bar"}})) == Json::object());
                    CHECK(j.value("/array"_json_pointer, Json({10, 100})) == Json({1, 2, 3}));

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

                SECTION("access on non-object type")
                {
                    SECTION("null")
                    {
                        Json j_nonobject(Json::value_t::null);
                        const Json j_nonobject_const(Json::value_t::null);
                        CHECK_THROWS_WITH_AS(j_nonobject.value("/foo"_json_pointer, 1), "[json.exception.type_error.306] cannot use value() with null", typename Json::type_error&);
                        CHECK_THROWS_WITH_AS(j_nonobject_const.value("/foo"_json_pointer, 1), "[json.exception.type_error.306] cannot use value() with null", typename Json::type_error&);
                    }

                    SECTION("boolean")
                    {
                        Json j_nonobject(Json::value_t::boolean);
                        const Json j_nonobject_const(Json::value_t::boolean);
                        CHECK_THROWS_WITH_AS(j_nonobject.value("/foo"_json_pointer, 1), "[json.exception.type_error.306] cannot use value() with boolean", typename Json::type_error&);
                        CHECK_THROWS_WITH_AS(j_nonobject_const.value("/foo"_json_pointer, 1), "[json.exception.type_error.306] cannot use value() with boolean", typename Json::type_error&);
                    }

                    SECTION("string")
                    {
                        Json j_nonobject(Json::value_t::string);
                        const Json j_nonobject_const(Json::value_t::string);
                        CHECK_THROWS_WITH_AS(j_nonobject.value("/foo"_json_pointer, 1), "[json.exception.type_error.306] cannot use value() with string", typename Json::type_error&);
                        CHECK_THROWS_WITH_AS(j_nonobject_const.value("/foo"_json_pointer, 1), "[json.exception.type_error.306] cannot use value() with string", typename Json::type_error&);
                    }

                    SECTION("array")
                    {
                        Json j_nonobject(Json::value_t::array);
                        const Json j_nonobject_const(Json::value_t::array);
                        CHECK_THROWS_WITH_AS(j_nonobject.value("/foo"_json_pointer, 1), "[json.exception.type_error.306] cannot use value() with array", typename Json::type_error&);
                        CHECK_THROWS_WITH_AS(j_nonobject_const.value("/foo"_json_pointer, 1), "[json.exception.type_error.306] cannot use value() with array", typename Json::type_error&);
                    }

                    SECTION("number (integer)")
                    {
                        Json j_nonobject(Json::value_t::number_integer);
                        const Json j_nonobject_const(Json::value_t::number_integer);
                        CHECK_THROWS_WITH_AS(j_nonobject.value("/foo"_json_pointer, 1), "[json.exception.type_error.306] cannot use value() with number", typename Json::type_error&);
                        CHECK_THROWS_WITH_AS(j_nonobject_const.value("/foo"_json_pointer, 1), "[json.exception.type_error.306] cannot use value() with number", typename Json::type_error&);
                    }

                    SECTION("number (unsigned)")
                    {
                        Json j_nonobject(Json::value_t::number_unsigned);
                        const Json j_nonobject_const(Json::value_t::number_unsigned);
                        CHECK_THROWS_WITH_AS(j_nonobject.value("/foo"_json_pointer, 1), "[json.exception.type_error.306] cannot use value() with number", typename Json::type_error&);
                        CHECK_THROWS_WITH_AS(j_nonobject_const.value("/foo"_json_pointer, 1), "[json.exception.type_error.306] cannot use value() with number", typename Json::type_error&);
                    }

                    SECTION("number (floating-point)")
                    {
                        Json j_nonobject(Json::value_t::number_float);
                        const Json j_nonobject_const(Json::value_t::number_float);
                        CHECK_THROWS_WITH_AS(j_nonobject.value("/foo"_json_pointer, 1), "[json.exception.type_error.306] cannot use value() with number", typename Json::type_error&);
                        CHECK_THROWS_WITH_AS(j_nonobject_const.value("/foo"_json_pointer, 1), "[json.exception.type_error.306] cannot use value() with number", typename Json::type_error&);
                    }
                }
            }
        }

        SECTION("non-const operator[]")
        {
            {
                Json j_null;
                CHECK(j_null.is_null());
                j_null["key"] = 1;
                CHECK(j_null.is_object());
                CHECK(j_null.size() == 1);
                j_null["key"] = 2;
                CHECK(j_null.size() == 1);
            }
#ifdef JSON_HAS_CPP_17
            {
                std::string_view key = "key";
                Json j_null;
                CHECK(j_null.is_null());
                j_null[key] = 1;
                CHECK(j_null.is_object());
                CHECK(j_null.size() == 1);
                j_null[key] = 2;
                CHECK(j_null.size() == 1);
            }
#endif
        }

        SECTION("front and back")
        {
            if (std::is_same<Json, nlohmann::ordered_json>::value)
            {
                // "integer" is the first key
                CHECK(j.front() == Json(1));
                CHECK(j_const.front() == Json(1));
                // "array" is last key
                CHECK(j.back() == Json({1, 2, 3}));
                CHECK(j_const.back() == Json({1, 2, 3}));
            }
            else
            {
                // "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[typename Json::object_t::key_type("integer")] == j["integer"]);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

#ifdef JSON_HAS_CPP_17
            SECTION("access within bounds (string_view)")
            {
                CHECK(j["integer"] == Json(1));
                CHECK(j[std::string_view("integer")] == j["integer"]);

                CHECK(j["unsigned"] == Json(1u));
                CHECK(j[std::string_view("unsigned")] == j["unsigned"]);

                CHECK(j["boolean"] == Json(true));
                CHECK(j[std::string_view("boolean")] == j["boolean"]);

                CHECK(j["null"] == Json(nullptr));
                CHECK(j[std::string_view("null")] == j["null"]);

                CHECK(j["string"] == Json("hello world"));
                CHECK(j[std::string_view("string")] == j["string"]);

                CHECK(j["floating"] == Json(42.23));
                CHECK(j[std::string_view("floating")] == j["floating"]);

                CHECK(j["object"] == Json::object());
                CHECK(j[std::string_view("object")] == j["object"]);

                CHECK(j["array"] == Json({1, 2, 3}));
                CHECK(j[std::string_view("array")] == j["array"]);

                CHECK(j_const["integer"] == Json(1));
                CHECK(j_const[std::string_view("integer")] == j["integer"]);

                CHECK(j_const["boolean"] == Json(true));
                CHECK(j_const[std::string_view("boolean")] == j["boolean"]);

                CHECK(j_const["null"] == Json(nullptr));
                CHECK(j_const[std::string_view("null")] == j["null"]);

                CHECK(j_const["string"] == Json("hello world"));
                CHECK(j_const[std::string_view("string")] == j["string"]);

                CHECK(j_const["floating"] == Json(42.23));
                CHECK(j_const[std::string_view("floating")] == j["floating"]);

                CHECK(j_const["object"] == Json::object());
                CHECK(j_const[std::string_view("object")] == j["object"]);

                CHECK(j_const["array"] == Json({1, 2, 3}));
                CHECK(j_const[std::string_view("array")] == j["array"]);
            }
#endif

            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[typename Json::object_t::key_type("foo")]);
                    CHECK_THROWS_WITH_AS(j_const_nonobject["foo"], "[json.exception.type_error.305] cannot use operator[] with a string argument with null", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_const_nonobject[typename Json::object_t::key_type("foo")], "[json.exception.type_error.305] cannot use operator[] with a string argument with null", typename Json::type_error&);

#ifdef JSON_HAS_CPP_17
                    CHECK_NOTHROW(j_nonobject2[std::string_view("foo")]);
                    CHECK_THROWS_WITH_AS(j_const_nonobject[std::string_view("foo")], "[json.exception.type_error.305] cannot use operator[] with a string argument with null", typename Json::type_error&);
#endif
                }

                SECTION("boolean")
                {
                    Json j_nonobject(Json::value_t::boolean);
                    const Json j_const_nonobject(j_nonobject);
                    CHECK_THROWS_WITH_AS(j_nonobject["foo"],
                                         "[json.exception.type_error.305] cannot use operator[] with a string argument with boolean", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_nonobject[typename Json::object_t::key_type("foo")],
                                         "[json.exception.type_error.305] cannot use operator[] with a string argument with boolean", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_const_nonobject["foo"],
                                         "[json.exception.type_error.305] cannot use operator[] with a string argument with boolean", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_const_nonobject[typename Json::object_t::key_type("foo")],
                                         "[json.exception.type_error.305] cannot use operator[] with a string argument with boolean", typename Json::type_error&);

#ifdef JSON_HAS_CPP_17
                    CHECK_THROWS_WITH_AS(j_nonobject[std::string_view("foo")], "[json.exception.type_error.305] cannot use operator[] with a string argument with boolean", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_const_nonobject[std::string_view("foo")], "[json.exception.type_error.305] cannot use operator[] with a string argument with boolean", typename Json::type_error&);
#endif
                }

                SECTION("string")
                {
                    Json j_nonobject(Json::value_t::string);
                    const Json j_const_nonobject(j_nonobject);
                    CHECK_THROWS_WITH_AS(j_nonobject["foo"],
                                         "[json.exception.type_error.305] cannot use operator[] with a string argument with string", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_nonobject[typename Json::object_t::key_type("foo")],
                                         "[json.exception.type_error.305] cannot use operator[] with a string argument with string", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_const_nonobject["foo"],
                                         "[json.exception.type_error.305] cannot use operator[] with a string argument with string", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_const_nonobject[typename Json::object_t::key_type("foo")],
                                         "[json.exception.type_error.305] cannot use operator[] with a string argument with string", typename Json::type_error&);

#ifdef JSON_HAS_CPP_17
                    CHECK_THROWS_WITH_AS(j_nonobject[std::string_view("foo")], "[json.exception.type_error.305] cannot use operator[] with a string argument with string", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_const_nonobject[std::string_view("foo")], "[json.exception.type_error.305] cannot use operator[] with a string argument with string", typename Json::type_error&);
#endif
                }

                SECTION("array")
                {
                    Json j_nonobject(Json::value_t::array);
                    const Json j_const_nonobject(j_nonobject);
                    CHECK_THROWS_WITH_AS(j_nonobject["foo"],
                                         "[json.exception.type_error.305] cannot use operator[] with a string argument with array", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_nonobject[typename Json::object_t::key_type("foo")], "[json.exception.type_error.305] cannot use operator[] with a string argument with array", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_const_nonobject["foo"],
                                         "[json.exception.type_error.305] cannot use operator[] with a string argument with array", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_const_nonobject[typename Json::object_t::key_type("foo")],
                                         "[json.exception.type_error.305] cannot use operator[] with a string argument with array", typename Json::type_error&);

#ifdef JSON_HAS_CPP_17
                    CHECK_THROWS_WITH_AS(j_nonobject[std::string_view("foo")], "[json.exception.type_error.305] cannot use operator[] with a string argument with array", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_const_nonobject[std::string_view("foo")], "[json.exception.type_error.305] cannot use operator[] with a string argument with array", typename Json::type_error&);
#endif
                }

                SECTION("number (integer)")
                {
                    Json j_nonobject(Json::value_t::number_integer);
                    const Json j_const_nonobject(j_nonobject);
                    CHECK_THROWS_WITH_AS(j_nonobject["foo"],
                                         "[json.exception.type_error.305] cannot use operator[] with a string argument with number", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_nonobject[typename Json::object_t::key_type("foo")],
                                         "[json.exception.type_error.305] cannot use operator[] with a string argument with number", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_const_nonobject["foo"],
                                         "[json.exception.type_error.305] cannot use operator[] with a string argument with number", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_const_nonobject[typename Json::object_t::key_type("foo")],
                                         "[json.exception.type_error.305] cannot use operator[] with a string argument with number", typename Json::type_error&);

#ifdef JSON_HAS_CPP_17
                    CHECK_THROWS_WITH_AS(j_nonobject[std::string_view("foo")], "[json.exception.type_error.305] cannot use operator[] with a string argument with number", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_const_nonobject[std::string_view("foo")], "[json.exception.type_error.305] cannot use operator[] with a string argument with number", typename Json::type_error&);
#endif
                }

                SECTION("number (unsigned)")
                {
                    Json j_nonobject(Json::value_t::number_unsigned);
                    const Json j_const_nonobject(j_nonobject);
                    CHECK_THROWS_WITH_AS(j_nonobject["foo"],
                                         "[json.exception.type_error.305] cannot use operator[] with a string argument with number", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_nonobject[typename Json::object_t::key_type("foo")],
                                         "[json.exception.type_error.305] cannot use operator[] with a string argument with number", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_const_nonobject["foo"],
                                         "[json.exception.type_error.305] cannot use operator[] with a string argument with number", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_const_nonobject[typename Json::object_t::key_type("foo")],
                                         "[json.exception.type_error.305] cannot use operator[] with a string argument with number", typename Json::type_error&);

#ifdef JSON_HAS_CPP_17
                    CHECK_THROWS_WITH_AS(j_nonobject[std::string_view("foo")], "[json.exception.type_error.305] cannot use operator[] with a string argument with number", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_const_nonobject[std::string_view("foo")], "[json.exception.type_error.305] cannot use operator[] with a string argument with number", typename Json::type_error&);
#endif
                }

                SECTION("number (floating-point)")
                {
                    Json j_nonobject(Json::value_t::number_float);
                    const Json j_const_nonobject(j_nonobject);
                    CHECK_THROWS_WITH_AS(j_nonobject["foo"],
                                         "[json.exception.type_error.305] cannot use operator[] with a string argument with number", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_nonobject[typename Json::object_t::key_type("foo")],
                                         "[json.exception.type_error.305] cannot use operator[] with a string argument with number", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_const_nonobject["foo"],
                                         "[json.exception.type_error.305] cannot use operator[] with a string argument with number", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_const_nonobject[typename Json::object_t::key_type("foo")],
                                         "[json.exception.type_error.305] cannot use operator[] with a string argument with number", typename Json::type_error&);

#ifdef JSON_HAS_CPP_17
                    CHECK_THROWS_WITH_AS(j_nonobject[std::string_view("foo")], "[json.exception.type_error.305] cannot use operator[] with a string argument with number", typename Json::type_error&);
                    CHECK_THROWS_WITH_AS(j_const_nonobject[std::string_view("foo")], "[json.exception.type_error.305] cannot use operator[] with a string argument with number", typename Json::type_error&);
#endif
                }
            }
        }

        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);
            }

#ifdef JSON_HAS_CPP_17
            SECTION("remove element by key (string_view)")
            {
                CHECK(j.find(std::string_view("integer")) != j.end());
                CHECK(j.erase(std::string_view("integer")) == 1);
                CHECK(j.find(std::string_view("integer")) == j.end());
                CHECK(j.erase(std::string_view("integer")) == 0);

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

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

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

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

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

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

                CHECK(j.find(std::string_view("array")) != j.end());
                CHECK(j.erase(std::string_view("array")) == 1);
                CHECK(j.find(std::string_view("array")) == j.end());
                CHECK(j.erase(std::string_view("array")) == 0);
            }
#endif

            SECTION("remove element by iterator")
            {
                SECTION("erase(begin())")
                {
                    {
                        Json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
                        typename 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}};
                        typename 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}};
                        typename 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}};
                        typename 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}};
                        typename 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}};
                        typename 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}};
                        typename Json::iterator it = jobject.find("b");
                        typename 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}};
                        typename Json::const_iterator it = jobject.find("b");
                        typename 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}};
                        typename 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}};
                        typename 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_WITH_AS(jobject.erase(jobject2.begin()),
                                             "[json.exception.invalid_iterator.202] iterator does not fit current value", typename Json::invalid_iterator&);
                        CHECK_THROWS_WITH_AS(jobject.erase(jobject.begin(), jobject2.end()),
                                             "[json.exception.invalid_iterator.203] iterators do not fit current value", typename Json::invalid_iterator&);
                        CHECK_THROWS_WITH_AS(jobject.erase(jobject2.begin(), jobject.end()),
                                             "[json.exception.invalid_iterator.203] iterators do not fit current value", typename Json::invalid_iterator&);
                        CHECK_THROWS_WITH_AS(jobject.erase(jobject2.begin(), jobject2.end()),
                                             "[json.exception.invalid_iterator.203] iterators do not fit current value", typename Json::invalid_iterator&);
                    }
                    {
                        Json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}, {"d", false}, {"e", true}};
                        Json jobject2 = {{"a", "a"}, {"b", 1}, {"c", 17u}};
                        CHECK_THROWS_WITH_AS(jobject.erase(jobject2.cbegin()),
                                             "[json.exception.invalid_iterator.202] iterator does not fit current value", typename Json::invalid_iterator&);
                        CHECK_THROWS_WITH_AS(jobject.erase(jobject.cbegin(), jobject2.cend()),
                                             "[json.exception.invalid_iterator.203] iterators do not fit current value", typename Json::invalid_iterator&);
                        CHECK_THROWS_WITH_AS(jobject.erase(jobject2.cbegin(), jobject.cend()),
                                             "[json.exception.invalid_iterator.203] iterators do not fit current value", typename Json::invalid_iterator&);
                        CHECK_THROWS_WITH_AS(jobject.erase(jobject2.cbegin(), jobject2.cend()),
                                             "[json.exception.invalid_iterator.203] iterators do not fit current value", typename Json::invalid_iterator&);
                    }
                }
            }

            SECTION("remove element by key in non-object type")
            {
                SECTION("null")
                {
                    Json j_nonobject(Json::value_t::null);
                    CHECK_THROWS_WITH_AS(j_nonobject.erase("foo"), "[json.exception.type_error.307] cannot use erase() with null", typename Json::type_error&);

#ifdef JSON_HAS_CPP_17
                    CHECK_THROWS_WITH_AS(j_nonobject.erase(std::string_view("foo")), "[json.exception.type_error.307] cannot use erase() with null", typename Json::type_error&);
#endif
                }

                SECTION("boolean")
                {
                    Json j_nonobject(Json::value_t::boolean);
                    CHECK_THROWS_WITH_AS(j_nonobject.erase("foo"), "[json.exception.type_error.307] cannot use erase() with boolean", typename Json::type_error&);

#ifdef JSON_HAS_CPP_17
                    CHECK_THROWS_WITH_AS(j_nonobject.erase(std::string_view("foo")), "[json.exception.type_error.307] cannot use erase() with boolean", typename Json::type_error&);
#endif
                }

                SECTION("string")
                {
                    Json j_nonobject(Json::value_t::string);
                    CHECK_THROWS_WITH_AS(j_nonobject.erase("foo"), "[json.exception.type_error.307] cannot use erase() with string", typename Json::type_error&);

#ifdef JSON_HAS_CPP_17
                    CHECK_THROWS_WITH_AS(j_nonobject.erase(std::string_view("foo")), "[json.exception.type_error.307] cannot use erase() with string", typename Json::type_error&);
#endif
                }

                SECTION("array")
                {
                    Json j_nonobject(Json::value_t::array);
                    CHECK_THROWS_WITH_AS(j_nonobject.erase("foo"), "[json.exception.type_error.307] cannot use erase() with array", typename Json::type_error&);

#ifdef JSON_HAS_CPP_17
                    CHECK_THROWS_WITH_AS(j_nonobject.erase(std::string_view("foo")), "[json.exception.type_error.307] cannot use erase() with array", typename Json::type_error&);
#endif
                }

                SECTION("number (integer)")
                {
                    Json j_nonobject(Json::value_t::number_integer);
                    CHECK_THROWS_WITH_AS(j_nonobject.erase("foo"), "[json.exception.type_error.307] cannot use erase() with number", typename Json::type_error&);

#ifdef JSON_HAS_CPP_17
                    CHECK_THROWS_WITH_AS(j_nonobject.erase(std::string_view("foo")), "[json.exception.type_error.307] cannot use erase() with number", typename Json::type_error&);
#endif
                }

                SECTION("number (floating-point)")
                {
                    Json j_nonobject(Json::value_t::number_float);
                    CHECK_THROWS_WITH_AS(j_nonobject.erase("foo"), "[json.exception.type_error.307] cannot use erase() with number", typename Json::type_error&);

#ifdef JSON_HAS_CPP_17
                    CHECK_THROWS_WITH_AS(j_nonobject.erase(std::string_view("foo")), "[json.exception.type_error.307] cannot use erase() with number", typename Json::type_error&);
#endif
                }
            }
        }

        SECTION("find an element in an object")
        {
            SECTION("existing element")
            {
                for (const 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));
                }
#ifdef JSON_HAS_CPP_17
                for (const std::string_view 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));
                }
#endif
            }

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

#ifdef JSON_HAS_CPP_17
                CHECK(j.find(std::string_view("foo")) == j.end());
                CHECK(j_const.find(std::string_view("foo")) == j_const.end());
#endif
            }

            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());

#ifdef JSON_HAS_CPP_17
                    CHECK(j_nonarray.find(std::string_view("foo")) == j_nonarray.end());
                    CHECK(j_nonarray_const.find(std::string_view("foo")) == j_nonarray_const.end());
#endif
                }

                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());

#ifdef JSON_HAS_CPP_17
                    CHECK(j_nonarray.find(std::string_view("foo")) == j_nonarray.end());
                    CHECK(j_nonarray_const.find(std::string_view("foo")) == j_nonarray_const.end());
#endif
                }

                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());

#ifdef JSON_HAS_CPP_17
                    CHECK(j_nonarray.find(std::string_view("foo")) == j_nonarray.end());
                    CHECK(j_nonarray_const.find(std::string_view("foo")) == j_nonarray_const.end());
#endif
                }

                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());

#ifdef JSON_HAS_CPP_17
                    CHECK(j_nonarray.find(std::string_view("foo")) == j_nonarray.end());
                    CHECK(j_nonarray_const.find(std::string_view("foo")) == j_nonarray_const.end());
#endif
                }

                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());

#ifdef JSON_HAS_CPP_17
                    CHECK(j_nonarray.find(std::string_view("foo")) == j_nonarray.end());
                    CHECK(j_nonarray_const.find(std::string_view("foo")) == j_nonarray_const.end());
#endif
                }

                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());

#ifdef JSON_HAS_CPP_17
                    CHECK(j_nonarray.find(std::string_view("foo")) == j_nonarray.end());
                    CHECK(j_nonarray_const.find(std::string_view("foo")) == j_nonarray_const.end());
#endif
                }

                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());

#ifdef JSON_HAS_CPP_17
                    CHECK(j_nonarray.find(std::string_view("foo")) == j_nonarray.end());
                    CHECK(j_nonarray_const.find(std::string_view("foo")) == j_nonarray_const.end());
#endif
                }

                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());

#ifdef JSON_HAS_CPP_17
                    CHECK(j_nonarray.find(std::string_view("foo")) == j_nonarray.end());
                    CHECK(j_nonarray_const.find(std::string_view("foo")) == j_nonarray_const.end());
#endif
                }
            }
        }

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

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

#ifdef JSON_HAS_CPP_17
                CHECK(j.count(std::string_view("foo")) == 0);
                CHECK(j_const.count(std::string_view("foo")) == 0);
#endif
            }

            SECTION("all types")
            {
                SECTION("null")
                {
                    Json j_nonobject(Json::value_t::null);
                    const Json j_nonobject_const(Json::value_t::null);

                    CHECK(j_nonobject.count("foo") == 0);
                    CHECK(j_nonobject_const.count("foo") == 0);

#ifdef JSON_HAS_CPP_17
                    CHECK(j.count(std::string_view("foo")) == 0);
                    CHECK(j_const.count(std::string_view("foo")) == 0);
#endif
                }

                SECTION("string")
                {
                    Json j_nonobject(Json::value_t::string);
                    const Json j_nonobject_const(Json::value_t::string);

                    CHECK(j_nonobject.count("foo") == 0);
                    CHECK(j_nonobject_const.count("foo") == 0);

#ifdef JSON_HAS_CPP_17
                    CHECK(j.count(std::string_view("foo")) == 0);
                    CHECK(j_const.count(std::string_view("foo")) == 0);
#endif
                }

                SECTION("object")
                {
                    Json j_nonobject(Json::value_t::object);
                    const Json j_nonobject_const(Json::value_t::object);

                    CHECK(j_nonobject.count("foo") == 0);
                    CHECK(j_nonobject_const.count("foo") == 0);

#ifdef JSON_HAS_CPP_17
                    CHECK(j.count(std::string_view("foo")) == 0);
                    CHECK(j_const.count(std::string_view("foo")) == 0);
#endif
                }

                SECTION("array")
                {
                    Json j_nonobject(Json::value_t::array);
                    const Json j_nonobject_const(Json::value_t::array);

                    CHECK(j_nonobject.count("foo") == 0);
                    CHECK(j_nonobject_const.count("foo") == 0);

#ifdef JSON_HAS_CPP_17
                    CHECK(j.count(std::string_view("foo")) == 0);
                    CHECK(j_const.count(std::string_view("foo")) == 0);
#endif
                }

                SECTION("boolean")
                {
                    Json j_nonobject(Json::value_t::boolean);
                    const Json j_nonobject_const(Json::value_t::boolean);

                    CHECK(j_nonobject.count("foo") == 0);
                    CHECK(j_nonobject_const.count("foo") == 0);

#ifdef JSON_HAS_CPP_17
                    CHECK(j.count(std::string_view("foo")) == 0);
                    CHECK(j_const.count(std::string_view("foo")) == 0);
#endif
                }

                SECTION("number (integer)")
                {
                    Json j_nonobject(Json::value_t::number_integer);
                    const Json j_nonobject_const(Json::value_t::number_integer);

                    CHECK(j_nonobject.count("foo") == 0);
                    CHECK(j_nonobject_const.count("foo") == 0);

#ifdef JSON_HAS_CPP_17
                    CHECK(j.count(std::string_view("foo")) == 0);
                    CHECK(j_const.count(std::string_view("foo")) == 0);
#endif
                }

                SECTION("number (unsigned)")
                {
                    Json j_nonobject(Json::value_t::number_unsigned);
                    const Json j_nonobject_const(Json::value_t::number_unsigned);

                    CHECK(j_nonobject.count("foo") == 0);
                    CHECK(j_nonobject_const.count("foo") == 0);

#ifdef JSON_HAS_CPP_17
                    CHECK(j.count(std::string_view("foo")) == 0);
                    CHECK(j_const.count(std::string_view("foo")) == 0);
#endif
                }

                SECTION("number (floating-point)")
                {
                    Json j_nonobject(Json::value_t::number_float);
                    const Json j_nonobject_const(Json::value_t::number_float);

                    CHECK(j_nonobject.count("foo") == 0);
                    CHECK(j_nonobject_const.count("foo") == 0);

#ifdef JSON_HAS_CPP_17
                    CHECK(j.count(std::string_view("foo")) == 0);
                    CHECK(j_const.count(std::string_view("foo")) == 0);
#endif
                }
            }
        }

        SECTION("check existence of key in an object")
        {
            SECTION("existing element")
            {
                for (const auto* key :
                        {"integer", "unsigned", "floating", "null", "string", "boolean", "object", "array"
                        })
                {
                    CHECK(j.contains(key) == true);
                    CHECK(j_const.contains(key) == true);
                }

#ifdef JSON_HAS_CPP_17
                for (const std::string_view key :
                        {"integer", "unsigned", "floating", "null", "string", "boolean", "object", "array"
                        })
                {
                    CHECK(j.contains(key) == true);
                    CHECK(j_const.contains(key) == true);
                }
#endif
            }

            SECTION("nonexisting element")
            {
                CHECK(j.contains("foo") == false);
                CHECK(j_const.contains("foo") == false);

#ifdef JSON_HAS_CPP_17
                CHECK(j.contains(std::string_view("foo")) == false);
                CHECK(j_const.contains(std::string_view("foo")) == false);
#endif
            }

            SECTION("all types")
            {
                SECTION("null")
                {
                    Json j_nonobject(Json::value_t::null);
                    const Json j_nonobject_const(Json::value_t::null);

                    CHECK(j_nonobject.contains("foo") == false);
                    CHECK(j_nonobject_const.contains("foo") == false);

#ifdef JSON_HAS_CPP_17
                    CHECK(j_nonobject.contains(std::string_view("foo")) == false);
                    CHECK(j_nonobject_const.contains(std::string_view("foo")) == false);
#endif
                }

                SECTION("string")
                {
                    Json j_nonobject(Json::value_t::string);
                    const Json j_nonobject_const(Json::value_t::string);

                    CHECK(j_nonobject.contains("foo") == false);
                    CHECK(j_nonobject_const.contains("foo") == false);

#ifdef JSON_HAS_CPP_17
                    CHECK(j_nonobject.contains(std::string_view("foo")) == false);
                    CHECK(j_nonobject_const.contains(std::string_view("foo")) == false);
#endif
                }

                SECTION("object")
                {
                    Json j_nonobject(Json::value_t::object);
                    const Json j_nonobject_const(Json::value_t::object);

                    CHECK(j_nonobject.contains("foo") == false);
                    CHECK(j_nonobject_const.contains("foo") == false);

#ifdef JSON_HAS_CPP_17
                    CHECK(j_nonobject.contains(std::string_view("foo")) == false);
                    CHECK(j_nonobject_const.contains(std::string_view("foo")) == false);
#endif
                }

                SECTION("array")
                {
                    Json j_nonobject(Json::value_t::array);
                    const Json j_nonobject_const(Json::value_t::array);

                    CHECK(j_nonobject.contains("foo") == false);
                    CHECK(j_nonobject_const.contains("foo") == false);

#ifdef JSON_HAS_CPP_17
                    CHECK(j_nonobject.contains(std::string_view("foo")) == false);
                    CHECK(j_nonobject_const.contains(std::string_view("foo")) == false);
#endif
                }

                SECTION("boolean")
                {
                    Json j_nonobject(Json::value_t::boolean);
                    const Json j_nonobject_const(Json::value_t::boolean);

                    CHECK(j_nonobject.contains("foo") == false);
                    CHECK(j_nonobject_const.contains("foo") == false);

#ifdef JSON_HAS_CPP_17
                    CHECK(j_nonobject.contains(std::string_view("foo")) == false);
                    CHECK(j_nonobject_const.contains(std::string_view("foo")) == false);
#endif
                }

                SECTION("number (integer)")
                {
                    Json j_nonobject(Json::value_t::number_integer);
                    const Json j_nonobject_const(Json::value_t::number_integer);

                    CHECK(j_nonobject.contains("foo") == false);
                    CHECK(j_nonobject_const.contains("foo") == false);

#ifdef JSON_HAS_CPP_17
                    CHECK(j_nonobject.contains(std::string_view("foo")) == false);
                    CHECK(j_nonobject_const.contains(std::string_view("foo")) == false);
#endif
                }

                SECTION("number (unsigned)")
                {
                    Json j_nonobject(Json::value_t::number_unsigned);
                    const Json j_nonobject_const(Json::value_t::number_unsigned);

                    CHECK(j_nonobject.contains("foo") == false);
                    CHECK(j_nonobject_const.contains("foo") == false);

#ifdef JSON_HAS_CPP_17
                    CHECK(j_nonobject.contains(std::string_view("foo")) == false);
                    CHECK(j_nonobject_const.contains(std::string_view("foo")) == false);
#endif
                }

                SECTION("number (floating-point)")
                {
                    Json j_nonobject(Json::value_t::number_float);
                    const Json j_nonobject_const(Json::value_t::number_float);
                    CHECK(j_nonobject.contains("foo") == false);
                    CHECK(j_nonobject_const.contains("foo") == false);
#ifdef JSON_HAS_CPP_17
                    CHECK(j_nonobject.contains(std::string_view("foo")) == false);
                    CHECK(j_nonobject_const.contains(std::string_view("foo")) == false);
#endif
                }
            }
        }
    }
}

#if !defined(JSON_NOEXCEPTION)
TEST_CASE_TEMPLATE("element access 2 (throwing tests)", Json, nlohmann::json, nlohmann::ordered_json)
{
    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 = {{"integer", 1}, {"unsigned", 1u}, {"floating", 42.23}, {"null", nullptr}, {"string", "hello world"}, {"boolean", true}, {"object", Json::object()}, {"array", {1, 2, 3}}};

        SECTION("access specified element with default value")
        {
            SECTION("given a JSON pointer")
            {
                SECTION("access non-existing value")
                {
                    CHECK(j.value("/not/existing"_json_pointer, 2) == 2);
                    CHECK(j.value("/not/existing"_json_pointer, 2u) == 2u);
                    CHECK(j.value("/not/existing"_json_pointer, false) == false);
                    CHECK(j.value("/not/existing"_json_pointer, "bar") == "bar");
                    CHECK(j.value("/not/existing"_json_pointer, 12.34) == Approx(12.34));
                    CHECK(j.value("/not/existing"_json_pointer, Json({{"foo", "bar"}})) == Json({{"foo", "bar"}}));
                    CHECK(j.value("/not/existing"_json_pointer, Json({10, 100})) == Json({10, 100}));

                    CHECK(j_const.value("/not/existing"_json_pointer, 2) == 2);
                    CHECK(j_const.value("/not/existing"_json_pointer, 2u) == 2u);
                    CHECK(j_const.value("/not/existing"_json_pointer, false) == false);
                    CHECK(j_const.value("/not/existing"_json_pointer, "bar") == "bar");
                    CHECK(j_const.value("/not/existing"_json_pointer, 12.34) == Approx(12.34));
                    CHECK(j_const.value("/not/existing"_json_pointer, Json({{"foo", "bar"}})) == Json({{"foo", "bar"}}));
                    CHECK(j_const.value("/not/existing"_json_pointer, Json({10, 100})) == Json({10, 100}));
                }
            }
        }
    }
}
#endif
