//     __ _____ _____ _____
//  __|  |   __|     |   | |  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 _azure_JSON_TESTS_PRIVATE
#include <azure/core/internal/json/json.hpp>
using Azure::Core::Json::_internal::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&);
          }
        }
      }
    }
  }
}
