//     __ _____ _____ _____
//  __|  |   __|     |   | |  JSON for Modern C++ (supporting code)
// |  |  |__   |  |  | | | |  version 3.11.3
// |_____|_____|_____|_|___|  https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT

#include "doctest_compatibility.h"

#define JSON_TESTS_PRIVATE
#include <nlohmann/json.hpp>
using nlohmann::json;

#include <deque>
#include <forward_list>
#include <fstream>
#include <list>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <valarray>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        SECTION("std::map<std::string, std::string> #600")
        {
            const std::map<std::string, std::string> m
            {
                {"a", "b"},
                {"c", "d"},
                {"e", "f"},
            };

            json const j(m);
            CHECK((j.get<decltype(m)>() == m));
        }

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

        SECTION("associative container literal")
        {
            json const 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 const a{};
            json const j(a);
            CHECK(j.type() == json::value_t::array);
        }

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

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

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

        SECTION("std::pair")
        {
            std::pair<float, std::string> const p{1.0f, "string"};
            json const j(p);

            CHECK(j.type() == json::value_t::array);
            CHECK(j.get<decltype(p)>() == p);
            REQUIRE(j.size() == 2);
            CHECK(j[0] == std::get<0>(p));
            CHECK(j[1] == std::get<1>(p));
        }

        SECTION("std::pair with discarded values")
        {
            json const j{1, 2.0, "string"};

            const auto p = j.get<std::pair<int, float>>();
            CHECK(p.first == j[0]);
            CHECK(p.second == j[1]);
        }

        SECTION("std::tuple")
        {
            const auto t = std::make_tuple(1.0, std::string{"string"}, 42, std::vector<int> {0, 1});
            json const j(t);

            CHECK(j.type() == json::value_t::array);
            REQUIRE(j.size() == 4);
            CHECK(j.get<decltype(t)>() == t);
            CHECK(j[0] == std::get<0>(t));
            CHECK(j[1] == std::get<1>(t));
            CHECK(j[2] == std::get<2>(t));
            CHECK(j[3][0] == 0);
            CHECK(j[3][1] == 1);
        }

        SECTION("std::tuple with discarded values")
        {
            json const j{1, 2.0, "string", 42};

            const auto t = j.get<std::tuple<int, float, std::string>>();
            CHECK(std::get<0>(t) == j[0]);
            CHECK(std::get<1>(t) == j[1]);
            // CHECK(std::get<2>(t) == j[2]); // commented out due to CI issue, see https://github.com/nlohmann/json/pull/3985 and https://github.com/nlohmann/json/issues/4025
        }

        SECTION("std::pair/tuple/array failures")
        {
            json const j{1};

            CHECK_THROWS_WITH_AS((j.get<std::pair<int, int>>()), "[json.exception.out_of_range.401] array index 1 is out of range", json::out_of_range&);
            CHECK_THROWS_WITH_AS((j.get<std::tuple<int, int>>()), "[json.exception.out_of_range.401] array index 1 is out of range", json::out_of_range&);
            CHECK_THROWS_WITH_AS((j.get<std::array<int, 3>>()), "[json.exception.out_of_range.401] array index 1 is out of range", json::out_of_range&);
        }

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

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

            const auto a2 = j.get<std::array<json, 6>>();
            CHECK(a2 == a);
        }

        SECTION("std::valarray<int>")
        {
            std::valarray<int> const va = {1, 2, 3, 4, 5};
            json const j(va);
            CHECK(j.type() == json::value_t::array);
            CHECK(j == json({1, 2, 3, 4, 5}));

            auto jva = j.get<std::valarray<int>>();
            CHECK(jva.size() == va.size());
            for (size_t i = 0; i < jva.size(); ++i)
            {
                CHECK(va[i] == jva[i]);
            }
        }

        SECTION("std::valarray<double>")
        {
            std::valarray<double> const va = {1.2, 2.3, 3.4, 4.5, 5.6};
            json const j(va);
            CHECK(j.type() == json::value_t::array);
            CHECK(j == json({1.2, 2.3, 3.4, 4.5, 5.6}));

            auto jva = j.get<std::valarray<double>>();
            CHECK(jva.size() == va.size());
            for (size_t i = 0; i < jva.size(); ++i)
            {
                CHECK(va[i] == jva[i]);
            }
        }

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

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

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

        SECTION("sequence container literal")
        {
            json const 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 const s{};
            json const j(s);
            CHECK(j.type() == json::value_t::string);
        }

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

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

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

        SECTION("char[]")
        {
            char const s[] {"Hello world"}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
            json const j(s);
            CHECK(j.type() == json::value_t::string);
            CHECK(j == j_reference);
        }

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

        SECTION("string literal")
        {
            json const 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 const b{};
            json const j(b);
            CHECK(j.type() == json::value_t::boolean);
        }

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

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

        SECTION("from std::vector<bool>::reference")
        {
            std::vector<bool> v{true};
            json const j(v[0]);
            CHECK(std::is_same<decltype(v[0]), std::vector<bool>::reference>::value);
            CHECK(j.type() == json::value_t::boolean);
        }

        SECTION("from std::vector<bool>::const_reference")
        {
            const std::vector<bool> v{true};
            json const j(v[0]);
            CHECK(std::is_same<decltype(v[0]), std::vector<bool>::const_reference>::value);
            CHECK(j.type() == json::value_t::boolean);
        }
    }

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

        SECTION("filled binary")
        {
            json::binary_t const b({1, 2, 3});
            json const j(b);
            CHECK(j.type() == json::value_t::binary);
        }
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        SECTION("integer literal without suffix")
        {
            json const 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 const 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 const 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 const n{};
            json const j(n);
            CHECK(j.type() == json::value_t::number_float);
        }

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

        SECTION("NaN")
        {
            // NaN is stored properly, but serialized to null
            json::number_float_t const n(std::numeric_limits<json::number_float_t>::quiet_NaN());
            json const j(n);
            CHECK(j.type() == json::value_t::number_float);

            // check round trip of NaN
            json::number_float_t const d{j};
            CHECK((std::isnan(d) && std::isnan(n)) == true);

            // check that NaN is serialized to null
            CHECK(j.dump() == "null");
        }

        SECTION("infinity")
        {
            // infinity is stored properly, but serialized to null
            json::number_float_t const n(std::numeric_limits<json::number_float_t>::infinity());
            json const j(n);
            CHECK(j.type() == json::value_t::number_float);

            // check round trip of infinity
            json::number_float_t const d{j};
            CHECK(d == n);

            // check that inf is serialized to null
            CHECK(j.dump() == "null");
        }
    }

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

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

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

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

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

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

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

    SECTION("create a container (array or object) from an initializer list")
    {
        SECTION("empty initializer list")
        {
            SECTION("explicit")
            {
                json const j(json::initializer_list_t {});
                CHECK(j.type() == json::value_t::object);
            }

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

        SECTION("one element")
        {
            SECTION("array")
            {
                SECTION("explicit")
                {
                    json const j(json::initializer_list_t {json(json::array_t())});
                    CHECK(j.type() == json::value_t::array);
                }

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

            SECTION("object")
            {
                SECTION("explicit")
                {
                    json const j(json::initializer_list_t {json(json::object_t())});
                    CHECK(j.type() == json::value_t::array);
                }

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

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

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

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

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

            SECTION("number (integer)")
            {
                SECTION("explicit")
                {
                    json const j(json::initializer_list_t {json(1)});
                    CHECK(j.type() == json::value_t::array);
                }

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

            SECTION("number (unsigned)")
            {
                SECTION("explicit")
                {
                    json const j(json::initializer_list_t {json(1u)});
                    CHECK(j.type() == json::value_t::array);
                }

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

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

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

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

            SECTION("implicit")
            {
                json const 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 const j { {"one", 1}, {"two", 1u}, {"three", 2.2}, {"four", false} };
                CHECK(j.type() == json::value_t::object);
            }

            SECTION("array")
            {
                json const 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 const j = json::object();
                CHECK(j.type() == json::value_t::object);
            }

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

            SECTION("object with error")
            {
                json _;
                CHECK_THROWS_WITH_AS(_ = json::object({ {"one", 1}, {"two", 1u}, {"three", 2.2}, {"four", false}, 13 }), "[json.exception.type_error.301] cannot create object from initializer list", json::type_error&);
            }

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

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

        SECTION("move from initializer_list")
        {
            SECTION("string")
            {
                SECTION("constructor with implicit types (array)")
                {
                    // This should break through any short string optimization in std::string
                    std::string source(1024, '!');
                    const auto* source_addr = source.data();
                    json j = {std::move(source)};
                    const auto* target_addr = j[0].get_ref<std::string const&>().data();
                    const bool success = (target_addr == source_addr);
                    CHECK(success);
                }

                SECTION("constructor with implicit types (object)")
                {
                    // This should break through any short string optimization in std::string
                    std::string source(1024, '!');
                    const auto* source_addr = source.data();
                    json j = {{"key", std::move(source)}};
                    const auto* target_addr = j["key"].get_ref<std::string const&>().data();
                    const bool success = (target_addr == source_addr);
                    CHECK(success);
                }

                SECTION("constructor with implicit types (object key)")
                {
                    // This should break through any short string optimization in std::string
                    std::string source(1024, '!');
                    const auto* source_addr = source.data();
                    json j = {{std::move(source), 42}};
                    const auto* target_addr = j.get_ref<json::object_t&>().begin()->first.data();
                    const bool success = (target_addr == source_addr);
                    CHECK(success);
                }
            }

            SECTION("array")
            {
                SECTION("constructor with implicit types (array)")
                {
                    json::array_t source = {1, 2, 3};
                    const auto* source_addr = source.data();
                    json j {std::move(source)};
                    const auto* target_addr = j[0].get_ref<json::array_t const&>().data();
                    const bool success = (target_addr == source_addr);
                    CHECK(success);
                }

                SECTION("constructor with implicit types (object)")
                {
                    json::array_t source = {1, 2, 3};
                    const auto* source_addr = source.data();
                    json const j {{"key", std::move(source)}};
                    const auto* target_addr = j["key"].get_ref<json::array_t const&>().data();
                    const bool success = (target_addr == source_addr);
                    CHECK(success);
                }

                SECTION("assignment with implicit types (array)")
                {
                    json::array_t source = {1, 2, 3};
                    const auto* source_addr = source.data();
                    json j = {std::move(source)};
                    const auto* target_addr = j[0].get_ref<json::array_t const&>().data();
                    const bool success = (target_addr == source_addr);
                    CHECK(success);
                }

                SECTION("assignment with implicit types (object)")
                {
                    json::array_t source = {1, 2, 3};
                    const auto* source_addr = source.data();
                    json j = {{"key", std::move(source)}};
                    const auto* target_addr = j["key"].get_ref<json::array_t const&>().data();
                    const bool success = (target_addr == source_addr);
                    CHECK(success);
                }
            }

            SECTION("object")
            {
                SECTION("constructor with implicit types (array)")
                {
                    json::object_t source = {{"hello", "world"}};
                    const json* source_addr = &source.at("hello");
                    json j {std::move(source)};
                    CHECK(&(j[0].get_ref<json::object_t const&>().at("hello")) == source_addr);
                }

                SECTION("constructor with implicit types (object)")
                {
                    json::object_t source = {{"hello", "world"}};
                    const json* source_addr = &source.at("hello");
                    json j {{"key", std::move(source)}};
                    CHECK(&(j["key"].get_ref<json::object_t const&>().at("hello")) == source_addr);
                }

                SECTION("assignment with implicit types (array)")
                {
                    json::object_t source = {{"hello", "world"}};
                    const json* source_addr = &source.at("hello");
                    json j = {std::move(source)};
                    CHECK(&(j[0].get_ref<json::object_t const&>().at("hello")) == source_addr);
                }

                SECTION("assignment with implicit types (object)")
                {
                    json::object_t source = {{"hello", "world"}};
                    const json* source_addr = &source.at("hello");
                    json j = {{"key", std::move(source)}};
                    CHECK(&(j["key"].get_ref<json::object_t const&>().at("hello")) == source_addr);
                }
            }

            SECTION("json")
            {
                SECTION("constructor with implicit types (array)")
                {
                    json source {1, 2, 3};
                    const json* source_addr = &source[0];
                    json j {std::move(source), {}};
                    CHECK(&j[0][0] == source_addr);
                }

                SECTION("constructor with implicit types (object)")
                {
                    json source {1, 2, 3};
                    const json* source_addr = &source[0];
                    json j {{"key", std::move(source)}};
                    CHECK(&j["key"][0] == source_addr);
                }

                SECTION("assignment with implicit types (array)")
                {
                    json source {1, 2, 3};
                    const json* source_addr = &source[0];
                    json j = {std::move(source), {}};
                    CHECK(&j[0][0] == source_addr);
                }

                SECTION("assignment with implicit types (object)")
                {
                    json source {1, 2, 3};
                    const json* source_addr = &source[0];
                    json j = {{"key", std::move(source)}};
                    CHECK(&j["key"][0] == source_addr);
                }
            }

        }
    }

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

        SECTION("cnt = 1")
        {
            json const v = {1, "foo", 34.23, {1, 2, 3}, {{"A", 1}, {"B", 2u}}};
            json const arr(1, v);
            CHECK(arr.size() == 1);
            for (const auto& x : arr)
            {
                CHECK(x == v);
            }
        }

        SECTION("cnt = 3")
        {
            json const v = {1, "foo", 34.23, {1, 2, 3}, {{"A", 1}, {"B", 2u}}};
            json const arr(3, v);
            CHECK(arr.size() == 3);
            for (const 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 const j_new(jobject.begin(), jobject.end());
                    CHECK(j_new == jobject);
                }
                {
                    json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
                    json const j_new(jobject.cbegin(), jobject.cend());
                    CHECK(j_new == jobject);
                }
            }

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

            SECTION("construct from subrange")
            {
                json const jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}, {"d", false}, {"e", true}};
                json const 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_WITH_AS(json(jobject.begin(), jobject2.end()), "[json.exception.invalid_iterator.201] iterators are not compatible", json::invalid_iterator&);
                    CHECK_THROWS_WITH_AS(json(jobject2.begin(), jobject.end()), "[json.exception.invalid_iterator.201] iterators are not compatible", json::invalid_iterator&);
                }
                {
                    json const jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}, {"d", false}, {"e", true}};
                    json const jobject2 = {{"a", "a"}, {"b", 1}, {"c", 17u}};
                    CHECK_THROWS_WITH_AS(json(jobject.cbegin(), jobject2.cend()), "[json.exception.invalid_iterator.201] iterators are not compatible", json::invalid_iterator&);
                    CHECK_THROWS_WITH_AS(json(jobject2.cbegin(), jobject.cend()), "[json.exception.invalid_iterator.201] iterators are not compatible", json::invalid_iterator&);
                }
            }
        }

        SECTION("array")
        {
            SECTION("json(begin(), end())")
            {
                {
                    json jarray = {1, 2, 3, 4, 5};
                    json const j_new(jarray.begin(), jarray.end());
                    CHECK(j_new == jarray);
                }
                {
                    json const jarray = {1, 2, 3, 4, 5};
                    json const 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 const jarray = {1, 2, 3, 4, 5};
                    json const j_new(jarray.cbegin(), jarray.cbegin());
                    CHECK(j_new == json::array());
                }
            }

            SECTION("construct from subrange")
            {
                {
                    json jarray = {1, 2, 3, 4, 5};
                    json const j_new(jarray.begin() + 1, jarray.begin() + 3);
                    CHECK(j_new == json({2, 3}));
                }
                {
                    json const jarray = {1, 2, 3, 4, 5};
                    json const 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_WITH_AS(json(jarray.begin(), jarray2.end()), "[json.exception.invalid_iterator.201] iterators are not compatible", json::invalid_iterator&);
                    CHECK_THROWS_WITH_AS(json(jarray2.begin(), jarray.end()), "[json.exception.invalid_iterator.201] iterators are not compatible", json::invalid_iterator&);
                }
                {
                    json const jarray = {1, 2, 3, 4};
                    json const jarray2 = {2, 3, 4, 5};
                    CHECK_THROWS_WITH_AS(json(jarray.cbegin(), jarray2.cend()), "[json.exception.invalid_iterator.201] iterators are not compatible", json::invalid_iterator&);
                    CHECK_THROWS_WITH_AS(json(jarray2.cbegin(), jarray.cend()), "[json.exception.invalid_iterator.201] iterators are not compatible", json::invalid_iterator&);
                }
            }
        }

        SECTION("other values")
        {
            SECTION("construct with two valid iterators")
            {
                SECTION("null")
                {
                    {
                        json j;
                        CHECK_THROWS_WITH_AS(json(j.begin(), j.end()), "[json.exception.invalid_iterator.206] cannot construct with iterators from null", json::invalid_iterator&);
                    }
                    {
                        json const j;
                        CHECK_THROWS_WITH_AS(json(j.cbegin(), j.cend()), "[json.exception.invalid_iterator.206] cannot construct with iterators from null", json::invalid_iterator&);
                    }
                }

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

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

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

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

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

                SECTION("binary")
                {
                    {
                        json j = json::binary({1, 2, 3});
                        json const j_new(j.begin(), j.end());
                        CHECK((j == j_new));
                    }
                    {
                        json const j = json::binary({1, 2, 3});
                        json const j_new(j.cbegin(), j.cend());
                        CHECK((j == j_new));
                    }
                }
            }

            SECTION("construct with two invalid iterators")
            {
                SECTION("string")
                {
                    {
                        json j = "foo";
                        CHECK_THROWS_WITH_AS(json(j.end(), j.end()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
                        CHECK_THROWS_WITH_AS(json(j.begin(), j.begin()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
                    }
                    {
                        json const j = "bar";
                        CHECK_THROWS_WITH_AS(json(j.cend(), j.cend()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
                        CHECK_THROWS_WITH_AS(json(j.cbegin(), j.cbegin()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
                    }
                }

                SECTION("number (boolean)")
                {
                    {
                        json j = false;
                        CHECK_THROWS_WITH_AS(json(j.end(), j.end()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
                        CHECK_THROWS_WITH_AS(json(j.begin(), j.begin()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
                    }
                    {
                        json const j = true;
                        CHECK_THROWS_WITH_AS(json(j.cend(), j.cend()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
                        CHECK_THROWS_WITH_AS(json(j.cbegin(), j.cbegin()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
                    }
                }

                SECTION("number (integer)")
                {
                    {
                        json j = 17;
                        CHECK_THROWS_WITH_AS(json(j.end(), j.end()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
                        CHECK_THROWS_WITH_AS(json(j.begin(), j.begin()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
                    }
                    {
                        json const j = 17;
                        CHECK_THROWS_WITH_AS(json(j.cend(), j.cend()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
                        CHECK_THROWS_WITH_AS(json(j.cbegin(), j.cbegin()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
                    }
                }

                SECTION("number (integer)")
                {
                    {
                        json j = 17u;
                        CHECK_THROWS_WITH_AS(json(j.end(), j.end()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
                        CHECK_THROWS_WITH_AS(json(j.begin(), j.begin()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
                    }
                    {
                        json const j = 17u;
                        CHECK_THROWS_WITH_AS(json(j.cend(), j.cend()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
                        CHECK_THROWS_WITH_AS(json(j.cbegin(), j.cbegin()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
                    }
                }

                SECTION("number (floating point)")
                {
                    {
                        json j = 23.42;
                        CHECK_THROWS_WITH_AS(json(j.end(), j.end()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
                        CHECK_THROWS_WITH_AS(json(j.begin(), j.begin()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
                    }
                    {
                        json const j = 23.42;
                        CHECK_THROWS_WITH_AS(json(j.cend(), j.cend()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
                        CHECK_THROWS_WITH_AS(json(j.cbegin(), j.cbegin()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
                    }
                }
            }
        }
    }
}
