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

#include "doctest_compatibility.h"

// disable -Wnoexcept due to class Evil
DOCTEST_GCC_SUPPRESS_WARNING_PUSH
DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept")

// skip tests if JSON_DisableEnumSerialization=ON (#4384)
#if defined(JSON_DISABLE_ENUM_SERIALIZATION) && (JSON_DISABLE_ENUM_SERIALIZATION == 1)
    #define SKIP_TESTS_FOR_ENUM_SERIALIZATION
#endif

#include <nlohmann/json.hpp>
using nlohmann::json;
#ifdef JSON_TEST_NO_GLOBAL_UDLS
    using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
#endif

#include <map>
#include <memory>
#include <string>
#include <utility>

namespace udt
{
enum class country
{
    china,
    france,
    russia
};

struct age
{
    int m_val;
    age(int rhs = 0) : m_val(rhs) {}
};

struct name
{
    std::string m_val;
    name(std::string rhs = "") : m_val(std::move(rhs)) {}
};

struct address
{
    std::string m_val;
    address(std::string rhs = "") : m_val(std::move(rhs)) {}
};

struct person
{
    age m_age{}; // NOLINT(readability-redundant-member-init)
    name m_name{}; // NOLINT(readability-redundant-member-init)
    country m_country{}; // NOLINT(readability-redundant-member-init)
    person() = default;
    person(const age& a, name  n, const country& c) : m_age(a), m_name(std::move(n)), m_country(c) {}
};

struct contact
{
    person m_person{}; // NOLINT(readability-redundant-member-init)
    address m_address{}; // NOLINT(readability-redundant-member-init)
    contact() = default;
    contact(person p, address a) : m_person(std::move(p)), m_address(std::move(a)) {}
};

enum class book_id : std::uint64_t;

struct contact_book
{
    name m_book_name{}; // NOLINT(readability-redundant-member-init)
    book_id m_book_id{};
    std::vector<contact> m_contacts{}; // NOLINT(readability-redundant-member-init)
    contact_book() = default;
    contact_book(name n, book_id i, std::vector<contact> c) : m_book_name(std::move(n)), m_book_id(i), m_contacts(std::move(c)) {}
};
} // namespace udt

// to_json methods
namespace udt
{
// templates because of the custom_json tests (see below)
template <typename BasicJsonType>
static void to_json(BasicJsonType& j, age a)
{
    j = a.m_val;
}

template <typename BasicJsonType>
static void to_json(BasicJsonType& j, const name& n)
{
    j = n.m_val;
}

template <typename BasicJsonType>
static void to_json(BasicJsonType& j, country c)
{
    switch (c)
    {
        case country::china:
            j = "中华人民共和国";
            return;
        case country::france:
            j = "France";
            return;
        case country::russia:
            j = "Российская Федерация";
            return;
        default:
            break;
    }
}

template <typename BasicJsonType>
static void to_json(BasicJsonType& j, const person& p)
{
    j = BasicJsonType{{"age", p.m_age}, {"name", p.m_name}, {"country", p.m_country}};
}

static void to_json(nlohmann::json& j, const address& a)
{
    j = a.m_val;
}

static void to_json(nlohmann::json& j, const contact& c)
{
    j = json{{"person", c.m_person}, {"address", c.m_address}};
}

static void to_json(nlohmann::json& j, const contact_book& cb)
{
    j = json{{"name", cb.m_book_name},
#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
        {"id", cb.m_book_id},
#endif
        {"contacts", cb.m_contacts}};
}

// operators
static bool operator==(age lhs, age rhs)
{
    return lhs.m_val == rhs.m_val;
}

static bool operator==(const address& lhs, const address& rhs)
{
    return lhs.m_val == rhs.m_val;
}

static bool operator==(const name& lhs, const name& rhs)
{
    return lhs.m_val == rhs.m_val;
}

static bool operator==(const person& lhs, const person& rhs)
{
    return std::tie(lhs.m_name, lhs.m_age) == std::tie(rhs.m_name, rhs.m_age);
}

static bool operator==(const contact& lhs, const contact& rhs)
{
    return std::tie(lhs.m_person, lhs.m_address) ==
           std::tie(rhs.m_person, rhs.m_address);
}

static bool operator==(const contact_book& lhs, const contact_book& rhs)
{
    return std::tie(lhs.m_book_name, lhs.m_book_id, lhs.m_contacts) ==
           std::tie(rhs.m_book_name, rhs.m_book_id, rhs.m_contacts);
}
} // namespace udt

// from_json methods
namespace udt
{
template <typename BasicJsonType>
static void from_json(const BasicJsonType& j, age& a)
{
    a.m_val = j.template get<int>();
}

template <typename BasicJsonType>
static void from_json(const BasicJsonType& j, name& n)
{
    n.m_val = j.template get<std::string>();
}

template <typename BasicJsonType>
static void from_json(const BasicJsonType& j, country& c)
{
    const auto str = j.template get<std::string>();
    const std::map<std::string, country> m =
    {
        {"中华人民共和国", country::china},
        {"France", country::france},
        {"Российская Федерация", country::russia}
    };

    const auto it = m.find(str);
    // TODO(nlohmann) test exceptions
    c = it->second;
}

template <typename BasicJsonType>
static void from_json(const BasicJsonType& j, person& p)
{
    p.m_age = j["age"].template get<age>();
    p.m_name = j["name"].template get<name>();
    p.m_country = j["country"].template get<country>();
}

static void from_json(const nlohmann::json& j, address& a)
{
    a.m_val = j.get<std::string>();
}

static void from_json(const nlohmann::json& j, contact& c)
{
    c.m_person = j["person"].get<person>();
    c.m_address = j["address"].get<address>();
}

static void from_json(const nlohmann::json& j, contact_book& cb)
{
    cb.m_book_name = j["name"].get<name>();
#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
    cb.m_book_id = j["id"].get<book_id>();
#endif
    cb.m_contacts = j["contacts"].get<std::vector<contact>>();
}
} // namespace udt

TEST_CASE("basic usage" * doctest::test_suite("udt"))
{

    // a bit narcissistic maybe :) ?
    const udt::age a
    {
        23
    };
    const udt::name n{"theo"};
    const udt::country c{udt::country::france};
    const udt::person sfinae_addict{a, n, c};
    const udt::person senior_programmer{{42}, {"王芳"}, udt::country::china};
    const udt::address addr{"Paris"};
    const udt::contact cpp_programmer{sfinae_addict, addr};
    const udt::book_id large_id{static_cast<udt::book_id>(static_cast<std::uint64_t>(1) << 63)}; // verify large unsigned enums are handled correctly
    const udt::contact_book book{{"C++"}, static_cast<udt::book_id>(42u), {cpp_programmer, {senior_programmer, addr}}};

    SECTION("conversion to json via free-functions")
    {
        CHECK(json(a) == json(23));
        CHECK(json(n) == json("theo"));
        CHECK(json(c) == json("France"));
        CHECK(json(sfinae_addict) == R"({"name":"theo", "age":23, "country":"France"})"_json);
        CHECK(json("Paris") == json(addr));
        CHECK(json(cpp_programmer) ==
              R"({"person" : {"age":23, "name":"theo", "country":"France"}, "address":"Paris"})"_json);
#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
        CHECK(json(large_id) == json(static_cast<std::uint64_t>(1) << 63));
        CHECK(json(large_id) > 0u);
        CHECK(to_string(json(large_id)) == "9223372036854775808");
        CHECK(json(large_id).is_number_unsigned());
#endif

#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
        CHECK(
            json(book) ==
            R"({"name":"C++", "id":42, "contacts" : [{"person" : {"age":23, "name":"theo", "country":"France"}, "address":"Paris"}, {"person" : {"age":42, "country":"中华人民共和国", "name":"王芳"}, "address":"Paris"}]})"_json);
#else
        CHECK(
            json(book) ==
            R"({"name":"C++", "contacts" : [{"person" : {"age":23, "name":"theo", "country":"France"}, "address":"Paris"}, {"person" : {"age":42, "country":"中华人民共和国", "name":"王芳"}, "address":"Paris"}]})"_json);
#endif

    }

    SECTION("conversion from json via free-functions")
    {
        const auto big_json =
            R"({"name":"C++", "id":42, "contacts" : [{"person" : {"age":23, "name":"theo", "country":"France"}, "address":"Paris"}, {"person" : {"age":42, "country":"中华人民共和国", "name":"王芳"}, "address":"Paris"}]})"_json;
        SECTION("via explicit calls to get")
        {
            const auto parsed_book = big_json.get<udt::contact_book>();
            const auto book_name = big_json["name"].get<udt::name>();
#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
            const auto book_id = big_json["id"].get<udt::book_id>();
#endif
            const auto contacts =
                big_json["contacts"].get<std::vector<udt::contact>>();
            const auto contact_json = big_json["contacts"].at(0);
            const auto contact = contact_json.get<udt::contact>();
            const auto person = contact_json["person"].get<udt::person>();
            const auto address = contact_json["address"].get<udt::address>();
            const auto age = contact_json["person"]["age"].get<udt::age>();
            const auto country =
                contact_json["person"]["country"].get<udt::country>();
            const auto name = contact_json["person"]["name"].get<udt::name>();

            CHECK(age == a);
            CHECK(name == n);
            CHECK(country == c);
            CHECK(address == addr);
            CHECK(person == sfinae_addict);
            CHECK(contact == cpp_programmer);
            CHECK(contacts == book.m_contacts);
            CHECK(book_name == udt::name{"C++"});
#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
            CHECK(book_id == book.m_book_id);
            CHECK(book == parsed_book);
#endif
        }

        SECTION("via explicit calls to get_to")
        {
            udt::person person;
            udt::name name;

            json person_json = big_json["contacts"][0]["person"];
            CHECK(person_json.get_to(person) == sfinae_addict);

            // correct reference gets returned
            person_json["name"].get_to(name).m_val = "new name";
            CHECK(name.m_val == "new name");
        }

#if JSON_USE_IMPLICIT_CONVERSIONS
        SECTION("implicit conversions")
        {
            const udt::contact_book parsed_book = big_json;
            const udt::name book_name = big_json["name"];
#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
            const udt::book_id book_id = big_json["id"];
#endif
            const std::vector<udt::contact> contacts = big_json["contacts"];
            const auto contact_json = big_json["contacts"].at(0);
            const udt::contact contact = contact_json;
            const udt::person person = contact_json["person"];
            const udt::address address = contact_json["address"];
            const udt::age age = contact_json["person"]["age"];
            const udt::country country = contact_json["person"]["country"];
            const udt::name name = contact_json["person"]["name"];

            CHECK(age == a);
            CHECK(name == n);
            CHECK(country == c);
            CHECK(address == addr);
            CHECK(person == sfinae_addict);
            CHECK(contact == cpp_programmer);
            CHECK(contacts == book.m_contacts);
            CHECK(book_name == udt::name{"C++"});
#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
            CHECK(book_id == static_cast<udt::book_id>(42u));
            CHECK(book == parsed_book);
#endif
        }
#endif
    }
}

namespace udt
{
struct legacy_type
{
    std::string number{}; // NOLINT(readability-redundant-member-init)
    legacy_type() = default;
    legacy_type(std::string n) : number(std::move(n)) {}
};
} // namespace udt

namespace nlohmann
{
template <typename T>
struct adl_serializer<std::shared_ptr<T>>
{
    static void to_json(json& j, const std::shared_ptr<T>& opt)
    {
        if (opt)
        {
            j = *opt;
        }
        else
        {
            j = nullptr;
        }
    }

    static void from_json(const json& j, std::shared_ptr<T>& opt)
    {
        if (j.is_null())
        {
            opt = nullptr;
        }
        else
        {
            opt.reset(new T(j.get<T>())); // NOLINT(cppcoreguidelines-owning-memory)
        }
    }
};

template <>
struct adl_serializer<udt::legacy_type>
{
    static void to_json(json& j, const udt::legacy_type& l)
    {
        j = std::stoi(l.number);
    }

    static void from_json(const json& j, udt::legacy_type& l)
    {
        l.number = std::to_string(j.get<int>());
    }
};
} // namespace nlohmann

TEST_CASE("adl_serializer specialization" * doctest::test_suite("udt"))
{
    SECTION("partial specialization")
    {
        SECTION("to_json")
        {
            std::shared_ptr<udt::person> optPerson;

            json j = optPerson;
            CHECK(j.is_null());

            optPerson.reset(new udt::person{{42}, {"John Doe"}, udt::country::russia}); // NOLINT(cppcoreguidelines-owning-memory,modernize-make-shared)
            j = optPerson;
            CHECK_FALSE(j.is_null());

            CHECK(j.get<udt::person>() == *optPerson);
        }

        SECTION("from_json")
        {
            auto person = udt::person{{42}, {"John Doe"}, udt::country::russia};
            json j = person;

            auto optPerson = j.get<std::shared_ptr<udt::person>>();
            REQUIRE(optPerson);
            CHECK(*optPerson == person);

            j = nullptr;
            optPerson = j.get<std::shared_ptr<udt::person>>();
            CHECK(!optPerson);
        }
    }

    SECTION("total specialization")
    {
        SECTION("to_json")
        {
            udt::legacy_type const lt{"4242"};

            json const j = lt;
            CHECK(j.get<int>() == 4242);
        }

        SECTION("from_json")
        {
            json const j = 4242;
            auto lt = j.get<udt::legacy_type>();
            CHECK(lt.number == "4242");
        }
    }
}

namespace nlohmann
{
template <>
struct adl_serializer<std::vector<float>>
{
    using type = std::vector<float>;
    static void to_json(json& j, const type& /*type*/)
    {
        j = "hijacked!";
    }

    static void from_json(const json& /*unnamed*/, type& opt)
    {
        opt = {42.0, 42.0, 42.0};
    }

    // preferred version
    static type from_json(const json& /*unnamed*/)
    {
        return {4.0, 5.0, 6.0};
    }
};
} // namespace nlohmann

TEST_CASE("even supported types can be specialized" * doctest::test_suite("udt"))
{
    json const j = std::vector<float> {1.0, 2.0, 3.0};
    CHECK(j.dump() == R"("hijacked!")");
    auto f = j.get<std::vector<float>>();
    // the single argument from_json method is preferred
    CHECK((f == std::vector<float> {4.0, 5.0, 6.0}));
}

namespace nlohmann
{
template <typename T>
struct adl_serializer<std::unique_ptr<T>>
{
    static void to_json(json& j, const std::unique_ptr<T>& opt)
    {
        if (opt)
        {
            j = *opt;
        }
        else
        {
            j = nullptr;
        }
    }

    // this is the overload needed for non-copyable types,
    static std::unique_ptr<T> from_json(const json& j)
    {
        if (j.is_null())
        {
            return nullptr;
        }

        return std::unique_ptr<T>(new T(j.get<T>()));
    }
};
} // namespace nlohmann

TEST_CASE("Non-copyable types" * doctest::test_suite("udt"))
{
    SECTION("to_json")
    {
        std::unique_ptr<udt::person> optPerson;

        json j = optPerson;
        CHECK(j.is_null());

        optPerson.reset(new udt::person{{42}, {"John Doe"}, udt::country::russia}); // NOLINT(cppcoreguidelines-owning-memory,modernize-make-unique)
        j = optPerson;
        CHECK_FALSE(j.is_null());

        CHECK(j.get<udt::person>() == *optPerson);
    }

    SECTION("from_json")
    {
        auto person = udt::person{{42}, {"John Doe"}, udt::country::russia};
        json j = person;

        auto optPerson = j.get<std::unique_ptr<udt::person>>();
        REQUIRE(optPerson);
        CHECK(*optPerson == person);

        j = nullptr;
        optPerson = j.get<std::unique_ptr<udt::person>>();
        CHECK(!optPerson);
    }
}

// custom serializer - advanced usage
// pack structs that are pod-types (but not scalar types)
// relies on adl for any other type
template <typename T, typename = void>
struct pod_serializer
{
    // use adl for non-pods, or scalar types
    template <
        typename BasicJsonType, typename U = T,
        typename std::enable_if <
            !(std::is_pod<U>::value && std::is_class<U>::value), int >::type = 0 >
    static void from_json(const BasicJsonType& j, U& t)
    {
        using nlohmann::from_json;
        from_json(j, t);
    }

    // special behaviour for pods
    template < typename BasicJsonType, typename U = T,
               typename std::enable_if <
                   std::is_pod<U>::value && std::is_class<U>::value, int >::type = 0 >
    static void from_json(const  BasicJsonType& j, U& t)
    {
        std::uint64_t value = 0;
        // The following block is no longer relevant in this serializer, make another one that shows the issue
        // the problem arises only when one from_json method is defined without any constraint
        //
        // Why cannot we simply use: j.get<std::uint64_t>() ?
        // Well, with the current experiment, the get method looks for a from_json
        // function, which we are currently defining!
        // This would end up in a stack overflow. Calling nlohmann::from_json is a
        // workaround (is it?).
        // I shall find a good way to avoid this once all constructors are converted
        // to free methods
        //
        // In short, constructing a json by constructor calls to_json
        // calling get calls from_json, for now, we cannot do this in custom
        // serializers
        nlohmann::from_json(j, value);
        auto* bytes = static_cast<char*>(static_cast<void*>(&value)); // NOLINT(bugprone-casting-through-void)
        std::memcpy(&t, bytes, sizeof(value));
    }

    template <
        typename BasicJsonType, typename U = T,
        typename std::enable_if <
            !(std::is_pod<U>::value && std::is_class<U>::value), int >::type = 0 >
    static void to_json(BasicJsonType& j, const  T& t)
    {
        using nlohmann::to_json;
        to_json(j, t);
    }

    template < typename BasicJsonType, typename U = T,
               typename std::enable_if <
                   std::is_pod<U>::value && std::is_class<U>::value, int >::type = 0 >
    static void to_json(BasicJsonType& j, const  T& t) noexcept
    {
        const auto* bytes = static_cast< const unsigned char*>(static_cast<const void*>(&t));  // NOLINT(bugprone-casting-through-void)
        std::uint64_t value = 0;
        std::memcpy(&value, bytes, sizeof(value));
        nlohmann::to_json(j, value);
    }
};

namespace udt
{
struct small_pod
{
    int begin;
    char middle;
    short end;
};

struct non_pod
{
    std::string s{}; // NOLINT(readability-redundant-member-init)
    non_pod() = default;
    non_pod(std::string S) : s(std::move(S)) {}
};

template <typename BasicJsonType>
static void to_json(BasicJsonType& j, const non_pod& np)
{
    j = np.s;
}

template <typename BasicJsonType>
static void from_json(const BasicJsonType& j, non_pod& np)
{
    np.s = j.template get<std::string>();
}

static bool operator==(small_pod lhs, small_pod rhs) noexcept
{
    return std::tie(lhs.begin, lhs.middle, lhs.end) ==
           std::tie(rhs.begin, rhs.middle, rhs.end);
}

static bool operator==(const  non_pod& lhs, const  non_pod& rhs) noexcept
{
    return lhs.s == rhs.s;
}

static std::ostream& operator<<(std::ostream& os, small_pod l)
{
    return os << "begin: " << l.begin << ", middle: " << l.middle << ", end: " << l.end;
}
} // namespace udt

TEST_CASE("custom serializer for pods" * doctest::test_suite("udt"))
{
    using custom_json =
        nlohmann::basic_json<std::map, std::vector, std::string, bool,
        std::int64_t, std::uint64_t, double, std::allocator, pod_serializer>;

    auto p = udt::small_pod{42, '/', 42};
    custom_json const j = p;

    auto p2 = j.get<udt::small_pod>();

    CHECK(p == p2);

    auto np = udt::non_pod{{"non-pod"}};
    custom_json const j2 = np;
    auto np2 = j2.get<udt::non_pod>();
    CHECK(np == np2);
}

template <typename T, typename>
struct another_adl_serializer;

using custom_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, double, std::allocator, another_adl_serializer>;

template <typename T, typename>
struct another_adl_serializer
{
    static void from_json(const custom_json& j, T& t)
    {
        using nlohmann::from_json;
        from_json(j, t);
    }

    static void to_json(custom_json& j, const T& t)
    {
        using nlohmann::to_json;
        to_json(j, t);
    }
};

TEST_CASE("custom serializer that does adl by default" * doctest::test_suite("udt"))
{
    auto me = udt::person{{23}, {"theo"}, udt::country::france};

    json const j = me;
    custom_json const cj = me;

    CHECK(j.dump() == cj.dump());

    CHECK(me == j.get<udt::person>());
    CHECK(me == cj.get<udt::person>());
}

TEST_CASE("different basic_json types conversions")
{
    SECTION("null")
    {
        json const j;
        const custom_json cj = j;
        CHECK(cj == nullptr);
    }

    SECTION("boolean")
    {
        json const j = true;
        const custom_json cj = j;
        CHECK(cj == true);
    }

    SECTION("discarded")
    {
        json const j(json::value_t::discarded);
        custom_json cj;
        CHECK_NOTHROW(cj = j);
        CHECK(cj.type() == custom_json::value_t::discarded);
    }

    SECTION("array")
    {
        json const j = {1, 2, 3};
        custom_json const cj = j;
        CHECK((cj == std::vector<int> {1, 2, 3}));
    }

    SECTION("integer")
    {
        json const j = 42;
        const custom_json cj = j;
        CHECK(cj == 42);
    }

    SECTION("float")
    {
        json const j = 42.0;
        const custom_json cj = j;
        CHECK(cj == 42.0);
    }

    SECTION("unsigned")
    {
        json const j = 42u;
        const custom_json cj = j;
        CHECK(cj == 42u);
    }

    SECTION("string")
    {
        json const j = "forty-two";
        const custom_json cj = j;
        CHECK(cj == "forty-two");
    }

    SECTION("binary")
    {
        json j = json::binary({1, 2, 3}, 42);
        custom_json cj = j;
        CHECK(cj.get_binary().subtype() == 42);
        const std::vector<std::uint8_t>& cv = cj.get_binary();
        std::vector<std::uint8_t> v = j.get_binary();
        CHECK(cv == v);
    }

    SECTION("object")
    {
        json const j = {{"forty", "two"}};
        const custom_json cj = j;
        auto m = j.get<std::map<std::string, std::string>>();
        CHECK(cj == m);
    }

    SECTION("get<custom_json>")
    {
        json const j = 42;
        const custom_json cj = j.get<custom_json>();
        CHECK(cj == 42);
    }
}

namespace
{
struct incomplete;

// std::is_constructible is broken on macOS' libc++
// use the cppreference implementation

template <typename T, typename = void>
struct is_constructible_patched : std::false_type {};

template <typename T>
struct is_constructible_patched<T, decltype(void(json(std::declval<T>())))> : std::true_type {};
} // namespace

TEST_CASE("an incomplete type does not trigger a compiler error in non-evaluated context" * doctest::test_suite("udt"))
{
    static_assert(!is_constructible_patched<json, incomplete>::value, "");
}

namespace
{
class Evil
{
  public:
    Evil() = default;
    template <typename T>
    Evil(const T& t) : m_i(sizeof(t))
    {
        static_cast<void>(t); // fix MSVC's C4100 warning
    }

    int m_i = 0;
};

void from_json(const json& /*unused*/, Evil& /*unused*/) {}
} // namespace

TEST_CASE("Issue #924")
{
    // Prevent get<std::vector<Evil>>() to throw
    auto j = json::array();

    CHECK_NOTHROW(j.get<Evil>());
    CHECK_NOTHROW(j.get<std::vector<Evil>>());

    // silence Wunused-template warnings
    const Evil e(1);
    CHECK(e.m_i >= 0);

    // suppress warning: function "<unnamed>::Evil::Evil(T) [with T=std::string]" was declared but never referenced [declared_but_not_referenced]
    const Evil e2(std::string("foo"));
    CHECK(e2.m_i >= 0);
}

TEST_CASE("Issue #1237")
{
    struct non_convertible_type {};
    static_assert(!std::is_convertible<json, non_convertible_type>::value, "");
}

namespace
{
class no_iterator_type
{
  public:
    no_iterator_type(std::initializer_list<int> l)
        : _v(l)
    {}

    std::vector<int>::const_iterator begin() const
    {
        return _v.begin();
    }

    std::vector<int>::const_iterator end() const
    {
        return _v.end();
    }

  private:
    std::vector<int> _v;
};
}  // namespace

TEST_CASE("compatible array type, without iterator type alias")
{
    no_iterator_type const vec{1, 2, 3};
    json const j = vec;
}

DOCTEST_GCC_SUPPRESS_WARNING_POP


#include <iostream>
#include <nlohmann/json.hpp>

using json = nlohmann::json;

int main()
{
    // create JSON object
    json object =
    {
        {"the good", "il buono"},
        {"the bad", "il cattivo"},
        {"the ugly", "il brutto"}
    };

    // output element with key "the ugly"
    std::cout << object.at("the ugly") << '\n';

    // change element with key "the bad"
    object.at("the bad") = "il cattivo";

    // output changed array
    std::cout << object << '\n';

    // exception type_error.304
    try
    {
        // use at() on a non-object type
        json str = "I am a string";
        str.at("the good") = "Another string";
    }
    catch (const json::type_error& e)
    {
        std::cout << e.what() << '\n';
    }

    // exception out_of_range.401
    try
    {
        // try to write at a nonexisting key
        object.at("the fast") = "il rapido";
    }
    catch (const json::out_of_range& e)
    {
        std::cout << e.what() << '\n';
    }
}


Promise.all([
  import('./module-and-worker'),
  import('./modules/module2'),
  import('./modules/module3'),
]).then((data) => {
  const _data = { ...data[0], ...data[1], ...data[2] }
  self.postMessage(_data)
})

// for sourcemap
console.log('emit-chunk-sub-worker.js')


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

#include "doctest_compatibility.h"

#include <nlohmann/json.hpp>
using nlohmann::json;
#ifdef JSON_TEST_NO_GLOBAL_UDLS
    using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
#endif

#include <fstream>
#include "make_test_data_available.hpp"

TEST_CASE("JSON patch")
{
    SECTION("examples from RFC 6902")
    {
        SECTION("4. Operations")
        {
            // the ordering of members in JSON objects is not significant:
            const json op1 = R"({ "op": "add", "path": "/a/b/c", "value": "foo" })"_json;
            const json op2 = R"({ "path": "/a/b/c", "op": "add", "value": "foo" })"_json;
            const json op3 = R"({ "value": "foo", "path": "/a/b/c", "op": "add" })"_json;

            // check if the operation objects are equivalent
            CHECK(op1 == op2);
            CHECK(op1 == op3);
        }

        SECTION("4.1 add")
        {
            json const patch1 = R"([{ "op": "add", "path": "/a/b", "value": [ "foo", "bar" ] }])"_json;

            // However, the object itself or an array containing it does need
            // to exist, and it remains an error for that not to be the case.
            // For example, an "add" with a target location of "/a/b" starting
            // with this document
            json const doc1 = R"({ "a": { "foo": 1 } })"_json;

            // is not an error, because "a" exists, and "b" will be added to
            // its value.
            CHECK_NOTHROW(doc1.patch(patch1));
            auto doc1_ans = R"(
                {
                    "a": {
                        "foo": 1,
                        "b": [ "foo", "bar" ]
                    }
                }
            )"_json;
            CHECK(doc1.patch(patch1) == doc1_ans);

            // It is an error in this document:
            json const doc2 = R"({ "q": { "bar": 2 } })"_json;

            // because "a" does not exist.
#if JSON_DIAGNOSTIC_POSITIONS
            CHECK_THROWS_WITH_AS(doc2.patch(patch1), "[json.exception.out_of_range.403] (bytes 0-21) key 'a' not found", json::out_of_range&);
#else
            CHECK_THROWS_WITH_AS(doc2.patch(patch1), "[json.exception.out_of_range.403] key 'a' not found", json::out_of_range&);
#endif

            json const doc3 = R"({ "a": {} })"_json;
            json const patch2 = R"([{ "op": "add", "path": "/a/b/c", "value": 1 }])"_json;

            // should cause an error because "b" does not exist in doc3
#if JSON_DIAGNOSTICS
            CHECK_THROWS_WITH_AS(doc3.patch(patch2), "[json.exception.out_of_range.403] (/a) key 'b' not found", json::out_of_range&);
#elif JSON_DIAGNOSTIC_POSITIONS
            CHECK_THROWS_WITH_AS(doc3.patch(patch2), "[json.exception.out_of_range.403] (bytes 7-9) key 'b' not found", json::out_of_range&);
#else
            CHECK_THROWS_WITH_AS(doc3.patch(patch2), "[json.exception.out_of_range.403] key 'b' not found", json::out_of_range&);
#endif
        }

        SECTION("4.2 remove")
        {
            // If removing an element from an array, any elements above the
            // specified index are shifted one position to the left.
            json const doc = {1, 2, 3, 4};
            json const patch = {{{"op", "remove"}, {"path", "/1"}}};
            CHECK(doc.patch(patch) == json({1, 3, 4}));
        }

        SECTION("A.1. Adding an Object Member")
        {
            // An example target JSON document:
            json const doc = R"(
                    { "foo": "bar"}
                )"_json;

            // A JSON Patch document:
            json const patch = R"(
                    [
                        { "op": "add", "path": "/baz", "value": "qux" }
                    ]
                )"_json;

            // The resulting JSON document:
            json expected = R"(
                    {
                        "baz": "qux",
                        "foo": "bar"
                    }
                )"_json;

            // check if patched value is as expected
            CHECK(doc.patch(patch) == expected);

            // check roundtrip
            CHECK(doc.patch(json::diff(doc, expected)) == expected);
        }

        SECTION("A.2. Adding an Array Element")
        {
            // An example target JSON document:
            json const doc = R"(
                    { "foo": [ "bar", "baz" ] }
                )"_json;

            // A JSON Patch document:
            json const patch = R"(
                    [
                        { "op": "add", "path": "/foo/1", "value": "qux" }
                    ]
                )"_json;

            // The resulting JSON document:
            json expected = R"(
                    { "foo": [ "bar", "qux", "baz" ] }
                )"_json;

            // check if patched value is as expected
            CHECK(doc.patch(patch) == expected);

            // check roundtrip
            CHECK(doc.patch(json::diff(doc, expected)) == expected);
        }

        SECTION("A.3. Removing an Object Member")
        {
            // An example target JSON document:
            json const doc = R"(
                    {
                        "baz": "qux",
                        "foo": "bar"
                    }
                )"_json;

            // A JSON Patch document:
            json const patch = R"(
                    [
                        { "op": "remove", "path": "/baz" }
                    ]
                )"_json;

            // The resulting JSON document:
            json expected = R"(
                    { "foo": "bar" }
                )"_json;

            // check if patched value is as expected
            CHECK(doc.patch(patch) == expected);

            // check roundtrip
            CHECK(doc.patch(json::diff(doc, expected)) == expected);
        }

        SECTION("A.4. Removing an Array Element")
        {
            // An example target JSON document:
            json const doc = R"(
                    { "foo": [ "bar", "qux", "baz" ] }
                )"_json;

            // A JSON Patch document:
            json const patch = R"(
                    [
                        { "op": "remove", "path": "/foo/1" }
                    ]
                )"_json;

            // The resulting JSON document:
            json expected = R"(
                    { "foo": [ "bar", "baz" ] }
                )"_json;

            // check if patched value is as expected
            CHECK(doc.patch(patch) == expected);

            // check roundtrip
            CHECK(doc.patch(json::diff(doc, expected)) == expected);
        }

        SECTION("A.5. Replacing a Value")
        {
            // An example target JSON document:
            json const doc = R"(
                    {
                        "baz": "qux",
                        "foo": "bar"
                    }
                )"_json;

            // A JSON Patch document:
            json const patch = R"(
                    [
                        { "op": "replace", "path": "/baz", "value": "boo" }
                    ]
                )"_json;

            json expected = R"(
                    {
                        "baz": "boo",
                        "foo": "bar"
                    }
                )"_json;

            // check if patched value is as expected
            CHECK(doc.patch(patch) == expected);

            // check roundtrip
            CHECK(doc.patch(json::diff(doc, expected)) == expected);
        }

        SECTION("A.6. Moving a Value")
        {
            // An example target JSON document:
            json const doc = R"(
                    {
                        "foo": {
                           "bar": "baz",
                            "waldo": "fred"
                        },
                        "qux": {
                            "corge": "grault"
                        }
                    }
                )"_json;

            // A JSON Patch document:
            json const patch = R"(
                    [
                        { "op": "move", "from": "/foo/waldo", "path": "/qux/thud" }
                    ]
                )"_json;

            // The resulting JSON document:
            json expected = R"(
                    {
                        "foo": {
                           "bar": "baz"
                        },
                        "qux": {
                            "corge": "grault",
                            "thud": "fred"
                        }
                    }
                )"_json;

            // check if patched value is as expected
            CHECK(doc.patch(patch) == expected);

            // check roundtrip
            CHECK(doc.patch(json::diff(doc, expected)) == expected);
        }

        SECTION("A.7. Moving a Value")
        {
            // An example target JSON document:
            json const doc = R"(
                    { "foo": [ "all", "grass", "cows", "eat" ] }
                )"_json;

            // A JSON Patch document:
            json const patch = R"(
                    [
                        { "op": "move", "from": "/foo/1", "path": "/foo/3" }
                    ]
                )"_json;

            // The resulting JSON document:
            json expected = R"(
                    { "foo": [ "all", "cows", "eat", "grass" ] }
                )"_json;

            // check if patched value is as expected
            CHECK(doc.patch(patch) == expected);

            // check roundtrip
            CHECK(doc.patch(json::diff(doc, expected)) == expected);
        }

        SECTION("A.8. Testing a Value: Success")
        {
            // An example target JSON document:
            json doc = R"(
                    {
                         "baz": "qux",
                         "foo": [ "a", 2, "c" ]
                    }
                )"_json;

            // A JSON Patch document that will result in successful evaluation:
            json const patch = R"(
                    [
                        { "op": "test", "path": "/baz", "value": "qux" },
                        { "op": "test", "path": "/foo/1", "value": 2 }
                    ]
                )"_json;

            // check if evaluation does not throw
            CHECK_NOTHROW(doc.patch(patch));
            // check if patched document is unchanged
            CHECK(doc.patch(patch) == doc);
        }

        SECTION("A.9. Testing a Value: Error")
        {
            // An example target JSON document:
            json const doc = R"(
                    { "baz": "qux" }
                )"_json;

            // A JSON Patch document that will result in an error condition:
            json patch = R"(
                    [
                        { "op": "test", "path": "/baz", "value": "bar" }
                    ]
                )"_json;

            // check that evaluation throws
            CHECK_THROWS_AS(doc.patch(patch), json::other_error&);
#if JSON_DIAGNOSTICS
            CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] (/0) unsuccessful: " + patch[0].dump());
#elif JSON_DIAGNOSTIC_POSITIONS
            CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] (bytes 47-95) unsuccessful: " + patch[0].dump());
#else
            CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] unsuccessful: " + patch[0].dump());
#endif
        }

        SECTION("A.10. Adding a Nested Member Object")
        {
            // An example target JSON document:
            json const doc = R"(
                    { "foo": "bar" }
                )"_json;

            // A JSON Patch document:
            json const patch = R"(
                    [
                        { "op": "add", "path": "/child", "value": { "grandchild": { } } }
                    ]
                )"_json;

            // The resulting JSON document:
            json expected = R"(
                {
                    "foo": "bar",
                    "child": {
                        "grandchild": {
                        }
                    }
                }
                )"_json;

            // check if patched value is as expected
            CHECK(doc.patch(patch) == expected);

            // check roundtrip
            CHECK(doc.patch(json::diff(doc, expected)) == expected);
        }

        SECTION("A.11. Ignoring Unrecognized Elements")
        {
            // An example target JSON document:
            json const doc = R"(
                    { "foo": "bar" }
                )"_json;

            // A JSON Patch document:
            json const patch = R"(
                    [
                        { "op": "add", "path": "/baz", "value": "qux", "xyz": 123 }
                    ]
                )"_json;

            json expected = R"(
                    {
                        "foo": "bar",
                        "baz": "qux"
                    }
                )"_json;

            // check if patched value is as expected
            CHECK(doc.patch(patch) == expected);

            // check roundtrip
            CHECK(doc.patch(json::diff(doc, expected)) == expected);
        }

        SECTION("A.12. Adding to a Nonexistent Target")
        {
            // An example target JSON document:
            json const doc = R"(
                    { "foo": "bar" }
                )"_json;

            // A JSON Patch document:
            json const patch = R"(
                    [
                        { "op": "add", "path": "/baz/bat", "value": "qux" }
                    ]
                )"_json;

            // This JSON Patch document, applied to the target JSON document
            // above, would result in an error (therefore, it would not be
            // applied), because the "add" operation's target location that
            // references neither the root of the document, nor a member of
            // an existing object, nor a member of an existing array.
#if JSON_DIAGNOSTIC_POSITIONS
            CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.403] (bytes 21-37) key 'baz' not found", json::out_of_range&);
#else
            CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.403] key 'baz' not found", json::out_of_range&);
#endif
        }

        // A.13. Invalid JSON Patch Document
        // not applicable

        SECTION("A.14. Escape Ordering")
        {
            // An example target JSON document:
            json const doc = R"(
                    {
                        "/": 9,
                        "~1": 10
                    }
                )"_json;

            // A JSON Patch document:
            json const patch = R"(
                    [
                        {"op": "test", "path": "/~01", "value": 10}
                    ]
                )"_json;

            json expected = R"(
                    {
                        "/": 9,
                        "~1": 10
                    }
                )"_json;

            // check if patched value is as expected
            CHECK(doc.patch(patch) == expected);

            // check roundtrip
            CHECK(doc.patch(json::diff(doc, expected)) == expected);
        }

        SECTION("A.15. Comparing Strings and Numbers")
        {
            // An example target JSON document:
            json const doc = R"(
                    {
                        "/": 9,
                        "~1": 10
                    }
                )"_json;

            // A JSON Patch document that will result in an error condition:
            json patch = R"(
                    [
                        {"op": "test", "path": "/~01", "value": "10"}
                    ]
                )"_json;

            // check that evaluation throws
            CHECK_THROWS_AS(doc.patch(patch), json::other_error&);
#if JSON_DIAGNOSTICS
            CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] (/0) unsuccessful: " + patch[0].dump());
#elif JSON_DIAGNOSTIC_POSITIONS
            CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] (bytes 47-92) unsuccessful: " + patch[0].dump());
#else
            CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] unsuccessful: " + patch[0].dump());
#endif
        }

        SECTION("A.16. Adding an Array Value")
        {
            // An example target JSON document:
            json const doc = R"(
                    { "foo": ["bar"] }
                )"_json;

            // A JSON Patch document:
            json const patch = R"(
                    [
                        { "op": "add", "path": "/foo/-", "value": ["abc", "def"] }
                    ]
                )"_json;

            // The resulting JSON document:
            json expected = R"(
                    { "foo": ["bar", ["abc", "def"]] }
                )"_json;

            // check if patched value is as expected
            CHECK(doc.patch(patch) == expected);

            // check roundtrip
            CHECK(doc.patch(json::diff(doc, expected)) == expected);
        }
    }

    SECTION("own examples")
    {
        SECTION("add")
        {
            SECTION("add to the root element")
            {
                // If the path is the root of the target document - the
                // specified value becomes the entire content of the target
                // document.

                // An example target JSON document:
                json const doc = 17;

                // A JSON Patch document:
                json const patch = R"(
                        [
                            { "op": "add", "path": "", "value": [1,2,3] }
                        ]
                    )"_json;

                // The resulting JSON document:
                json expected = {1, 2, 3};

                // check if patched value is as expected
                CHECK(doc.patch(patch) == expected);

                // check roundtrip
                CHECK(doc.patch(json::diff(doc, expected)) == expected);
            }

            SECTION("add to end of the array")
            {
                // The specified index MUST NOT be greater than the number of
                // elements in the array. The example below uses and index of
                // exactly the number of elements in the array which is legal.

                // An example target JSON document:
                json const doc = {0, 1, 2};

                // A JSON Patch document:
                json const patch = R"(
                    [
                        { "op": "add", "path": "/3", "value": 3 }
                    ]
                )"_json;

                // The resulting JSON document:
                json expected = {0, 1, 2, 3};

                // check if patched value is as expected
                CHECK(doc.patch(patch) == expected);

                // check roundtrip
                CHECK(doc.patch(json::diff(doc, expected)) == expected);
            }
        }

        SECTION("copy")
        {
            // An example target JSON document:
            json const doc = R"(
                {
                    "foo": {
                        "bar": "baz",
                        "waldo": "fred"
                    },
                    "qux": {
                       "corge": "grault"
                    }
                }
            )"_json;

            // A JSON Patch document:
            json const patch = R"(
                [
                    { "op": "copy", "from": "/foo/waldo", "path": "/qux/thud" }
                ]
            )"_json;

            // The resulting JSON document:
            json expected = R"(
                {
                    "foo": {
                        "bar": "baz",
                        "waldo": "fred"
                    },
                    "qux": {
                       "corge": "grault",
                       "thud": "fred"
                    }
                }
            )"_json;

            // check if patched value is as expected
            CHECK(doc.patch(patch) == expected);

            // check roundtrip
            CHECK(doc.patch(json::diff(doc, expected)) == expected);
        }

        SECTION("replace")
        {
            json const j = "string";
            json const patch = {{{"op", "replace"}, {"path", ""}, {"value", 1}}};
            CHECK(j.patch(patch) == json(1));
        }

        SECTION("documentation GIF")
        {
            {
                // a JSON patch
                json const p1 = R"(
                     [{"op": "add", "path": "/GB", "value": "London"}]
                    )"_json;

                // a JSON value
                json const source = R"(
                      {"D": "Berlin", "F": "Paris"}
                    )"_json;

                // apply the patch
                const json target = source.patch(p1);
                // target = { "D": "Berlin", "F": "Paris", "GB": "London" }
                CHECK(target == R"({ "D": "Berlin", "F": "Paris", "GB": "London" })"_json);

                // create a diff from two JSONs
                const json p2 = json::diff(target, source); // NOLINT(readability-suspicious-call-argument)
                // p2 = [{"op": "delete", "path": "/GB"}]
                CHECK(p2 == R"([{"op":"remove","path":"/GB"}])"_json);
            }
            {
                // a JSON value
                json j = {"good", "bad", "ugly"};

                // a JSON pointer
                auto ptr = json::json_pointer("/2");

                // use to access elements
                j[ptr] = {{"it", "cattivo"}};
                CHECK(j == R"(["good","bad",{"it":"cattivo"}])"_json);

                // use user-defined string literal
                j["/2/en"_json_pointer] = "ugly";
                CHECK(j == R"(["good","bad",{"en":"ugly","it":"cattivo"}])"_json);

                const json flat = j.flatten();
                CHECK(flat == R"({"/0":"good","/1":"bad","/2/en":"ugly","/2/it":"cattivo"})"_json);
            }
        }
    }

    SECTION("errors")
    {
        SECTION("unknown operation")
        {
            SECTION("not an array")
            {
                json const j;
                json const patch = {{"op", "add"}, {"path", ""}, {"value", 1}};
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.104] parse error: JSON patch must be an array of objects", json::parse_error&);
            }

            SECTION("not an array of objects")
            {
                json const j;
                json const patch = {"op", "add", "path", "", "value", 1};
#if JSON_DIAGNOSTICS
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.104] parse error: (/0) JSON patch must be an array of objects", json::parse_error&);
#else
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.104] parse error: JSON patch must be an array of objects", json::parse_error&);
#endif
            }

            SECTION("missing 'op'")
            {
                json const j;
                json const patch = {{{"foo", "bar"}}};
#if JSON_DIAGNOSTICS
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation must have member 'op'", json::parse_error&);
#else
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation must have member 'op'", json::parse_error&);
#endif
            }

            SECTION("non-string 'op'")
            {
                json const j;
                json const patch = {{{"op", 1}}};
#if JSON_DIAGNOSTICS
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation must have string member 'op'", json::parse_error&);
#else
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation must have string member 'op'", json::parse_error&);
#endif
            }

            SECTION("invalid operation")
            {
                json const j;
                json const patch = {{{"op", "foo"}, {"path", ""}}};
#if JSON_DIAGNOSTICS
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation value 'foo' is invalid", json::parse_error&);
#else
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation value 'foo' is invalid", json::parse_error&);
#endif
            }
        }

        SECTION("add")
        {
            SECTION("missing 'path'")
            {
                json const j;
                json const patch = {{{"op", "add"}}};
#if JSON_DIAGNOSTICS
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'add' must have member 'path'", json::parse_error&);
#else
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'add' must have member 'path'", json::parse_error&);
#endif
            }

            SECTION("non-string 'path'")
            {
                json const j;
                json const patch = {{{"op", "add"}, {"path", 1}}};
#if JSON_DIAGNOSTICS
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'add' must have string member 'path'", json::parse_error&);
#else
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'add' must have string member 'path'", json::parse_error&);
#endif
            }

            SECTION("missing 'value'")
            {
                json const j;
                json const patch = {{{"op", "add"}, {"path", ""}}};
#if JSON_DIAGNOSTICS
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'add' must have member 'value'", json::parse_error&);
#else
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'add' must have member 'value'", json::parse_error&);
#endif
            }

            SECTION("invalid array index")
            {
                json const j = {1, 2};
                json const patch = {{{"op", "add"}, {"path", "/4"}, {"value", 4}}};
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.out_of_range.401] array index 4 is out of range", json::out_of_range&);
            }
        }

        SECTION("remove")
        {
            SECTION("missing 'path'")
            {
                json const j;
                json const patch = {{{"op", "remove"}}};
#if JSON_DIAGNOSTICS
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'remove' must have member 'path'", json::parse_error&);
#else
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'remove' must have member 'path'", json::parse_error&);
#endif
            }

            SECTION("non-string 'path'")
            {
                json const j;
                json const patch = {{{"op", "remove"}, {"path", 1}}};
#if JSON_DIAGNOSTICS
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'remove' must have string member 'path'", json::parse_error&);
#else
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'remove' must have string member 'path'", json::parse_error&);
#endif
            }

            SECTION("nonexisting target location (array)")
            {
                json const j = {1, 2, 3};
                json const patch = {{{"op", "remove"}, {"path", "/17"}}};
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.out_of_range.401] array index 17 is out of range", json::out_of_range&);
            }

            SECTION("nonexisting target location (object)")
            {
                json const j = {{"foo", 1}, {"bar", 2}};
                json const patch = {{{"op", "remove"}, {"path", "/baz"}}};
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.out_of_range.403] key 'baz' not found", json::out_of_range&);
            }

            SECTION("root element as target location")
            {
                json const j = "string";
                json const patch = {{{"op", "remove"}, {"path", ""}}};
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.out_of_range.405] JSON pointer has no parent", json::out_of_range&);
            }
        }

        SECTION("replace")
        {
            SECTION("missing 'path'")
            {
                json const j;
                json const patch = {{{"op", "replace"}}};
#if JSON_DIAGNOSTICS
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'replace' must have member 'path'", json::parse_error&);
#else
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'replace' must have member 'path'", json::parse_error&);
#endif
            }

            SECTION("non-string 'path'")
            {
                json const j;
                json const patch = {{{"op", "replace"}, {"path", 1}}};
#if JSON_DIAGNOSTICS
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'replace' must have string member 'path'", json::parse_error&);
#else
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'replace' must have string member 'path'", json::parse_error&);
#endif
            }

            SECTION("missing 'value'")
            {
                json const j;
                json const patch = {{{"op", "replace"}, {"path", ""}}};
#if JSON_DIAGNOSTICS
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'replace' must have member 'value'", json::parse_error&);
#else
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'replace' must have member 'value'", json::parse_error&);
#endif
            }

            SECTION("nonexisting target location (array)")
            {
                json const j = {1, 2, 3};
                json const patch = {{{"op", "replace"}, {"path", "/17"}, {"value", 19}}};
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.out_of_range.401] array index 17 is out of range", json::out_of_range&);
            }

            SECTION("nonexisting target location (object)")
            {
                json const j = {{"foo", 1}, {"bar", 2}};
                json const patch = {{{"op", "replace"}, {"path", "/baz"}, {"value", 3}}};
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.out_of_range.403] key 'baz' not found", json::out_of_range&);
            }
        }

        SECTION("move")
        {
            SECTION("missing 'path'")
            {
                json const j;
                json const patch = {{{"op", "move"}}};
#if JSON_DIAGNOSTICS
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'move' must have member 'path'", json::parse_error&);
#else
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'move' must have member 'path'", json::parse_error&);
#endif
            }

            SECTION("non-string 'path'")
            {
                json const j;
                json const patch = {{{"op", "move"}, {"path", 1}}};
#if JSON_DIAGNOSTICS
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'move' must have string member 'path'", json::parse_error&);
#else
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'move' must have string member 'path'", json::parse_error&);
#endif
            }

            SECTION("missing 'from'")
            {
                json const j;
                json const patch = {{{"op", "move"}, {"path", ""}}};
                CHECK_THROWS_AS(j.patch(patch), json::parse_error&);
#if JSON_DIAGNOSTICS
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'move' must have member 'from'", json::parse_error&);
#else
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'move' must have member 'from'", json::parse_error&);
#endif
            }

            SECTION("non-string 'from'")
            {
                json const j;
                json const patch = {{{"op", "move"}, {"path", ""}, {"from", 1}}};
                CHECK_THROWS_AS(j.patch(patch), json::parse_error&);
#if JSON_DIAGNOSTICS
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'move' must have string member 'from'", json::parse_error&);
#else
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'move' must have string member 'from'", json::parse_error&);
#endif
            }

            SECTION("nonexisting from location (array)")
            {
                json const j = {1, 2, 3};
                json const patch = {{{"op", "move"}, {"path", "/0"}, {"from", "/5"}}};
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.out_of_range.401] array index 5 is out of range", json::out_of_range&);
            }

            SECTION("nonexisting from location (object)")
            {
                json const j = {{"foo", 1}, {"bar", 2}};
                json const patch = {{{"op", "move"}, {"path", "/baz"}, {"from", "/baz"}}};
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.out_of_range.403] key 'baz' not found", json::out_of_range&);
            }
        }

        SECTION("copy")
        {
            SECTION("missing 'path'")
            {
                json const j;
                json const patch = {{{"op", "copy"}}};
#if JSON_DIAGNOSTICS
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'copy' must have member 'path'", json::parse_error&);
#else
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'copy' must have member 'path'", json::parse_error&);
#endif
            }

            SECTION("non-string 'path'")
            {
                json const j;
                json const patch = {{{"op", "copy"}, {"path", 1}}};
#if JSON_DIAGNOSTICS
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'copy' must have string member 'path'", json::parse_error&);
#else
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'copy' must have string member 'path'", json::parse_error&);
#endif
            }

            SECTION("missing 'from'")
            {
                json const j;
                json const patch = {{{"op", "copy"}, {"path", ""}}};
#if JSON_DIAGNOSTICS
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'copy' must have member 'from'", json::parse_error&);
#else
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'copy' must have member 'from'", json::parse_error&);
#endif
            }

            SECTION("non-string 'from'")
            {
                json const j;
                json const patch = {{{"op", "copy"}, {"path", ""}, {"from", 1}}};
#if JSON_DIAGNOSTICS
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'copy' must have string member 'from'", json::parse_error&);
#else
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'copy' must have string member 'from'", json::parse_error&);
#endif
            }

            SECTION("nonexisting from location (array)")
            {
                json const j = {1, 2, 3};
                json const patch = {{{"op", "copy"}, {"path", "/0"}, {"from", "/5"}}};
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.out_of_range.401] array index 5 is out of range", json::out_of_range&);
            }

            SECTION("nonexisting from location (object)")
            {
                json const j = {{"foo", 1}, {"bar", 2}};
                json const patch = {{{"op", "copy"}, {"path", "/fob"}, {"from", "/baz"}}};
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.out_of_range.403] key 'baz' not found", json::out_of_range&);
            }
        }

        SECTION("test")
        {
            SECTION("missing 'path'")
            {
                json const j;
                json const patch = {{{"op", "test"}}};
#if JSON_DIAGNOSTICS
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'test' must have member 'path'", json::parse_error&);
#else
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'test' must have member 'path'", json::parse_error&);
#endif
            }

            SECTION("non-string 'path'")
            {
                json const j;
                json const patch = {{{"op", "test"}, {"path", 1}}};
#if JSON_DIAGNOSTICS
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'test' must have string member 'path'", json::parse_error&);
#else
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'test' must have string member 'path'", json::parse_error&);
#endif
            }

            SECTION("missing 'value'")
            {
                json const j;
                json const patch = {{{"op", "test"}, {"path", ""}}};
#if JSON_DIAGNOSTICS
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'test' must have member 'value'", json::parse_error&);
#else
                CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'test' must have member 'value'", json::parse_error&);
#endif
            }
        }
    }

    SECTION("Examples from jsonpatch.com")
    {
        SECTION("Simple Example")
        {
            // The original document
            json const doc = R"(
                {
                  "baz": "qux",
                  "foo": "bar"
                }
            )"_json;

            // The patch
            json const patch = R"(
                [
                  { "op": "replace", "path": "/baz", "value": "boo" },
                  { "op": "add", "path": "/hello", "value": ["world"] },
                  { "op": "remove", "path": "/foo"}
                ]
            )"_json;

            // The result
            json result = R"(
                {
                   "baz": "boo",
                   "hello": ["world"]
                }
            )"_json;

            // check if patched value is as expected
            CHECK(doc.patch(patch) == result);

            // check roundtrip
            CHECK(doc.patch(json::diff(doc, result)) == result);
        }

        SECTION("Operations")
        {
            // The original document
            json const doc = R"(
                {
                  "biscuits": [
                    {"name":"Digestive"},
                    {"name": "Choco Liebniz"}
                  ]
                }
            )"_json;

            SECTION("add")
            {
                // The patch
                json const patch = R"(
                    [
                        {"op": "add", "path": "/biscuits/1", "value": {"name": "Ginger Nut"}}
                    ]
                )"_json;

                // The result
                json result = R"(
                    {
                      "biscuits": [
                        {"name": "Digestive"},
                        {"name": "Ginger Nut"},
                        {"name": "Choco Liebniz"}
                      ]
                    }
                )"_json;

                // check if patched value is as expected
                CHECK(doc.patch(patch) == result);

                // check roundtrip
                CHECK(doc.patch(json::diff(doc, result)) == result);
            }

            SECTION("remove")
            {
                // The patch
                json const patch = R"(
                    [
                        {"op": "remove", "path": "/biscuits"}
                    ]
                )"_json;

                // The result
                json result = R"(
                    {}
                )"_json;

                // check if patched value is as expected
                CHECK(doc.patch(patch) == result);

                // check roundtrip
                CHECK(doc.patch(json::diff(doc, result)) == result);
            }

            SECTION("replace")
            {
                // The patch
                json const patch = R"(
                    [
                        {"op": "replace", "path": "/biscuits/0/name", "value": "Chocolate Digestive"}
                    ]
                )"_json;

                // The result
                json result = R"(
                    {
                      "biscuits": [
                        {"name": "Chocolate Digestive"},
                        {"name": "Choco Liebniz"}
                      ]
                    }
                )"_json;

                // check if patched value is as expected
                CHECK(doc.patch(patch) == result);

                // check roundtrip
                CHECK(doc.patch(json::diff(doc, result)) == result);
            }

            SECTION("copy")
            {
                // The patch
                json const patch = R"(
                    [
                        {"op": "copy", "from": "/biscuits/0", "path": "/best_biscuit"}
                    ]
                )"_json;

                // The result
                json result = R"(
                    {
                      "biscuits": [
                        {"name": "Digestive"},
                        {"name": "Choco Liebniz"}
                      ],
                      "best_biscuit": {
                        "name": "Digestive"
                      }
                    }
                )"_json;

                // check if patched value is as expected
                CHECK(doc.patch(patch) == result);

                // check roundtrip
                CHECK(doc.patch(json::diff(doc, result)) == result);
            }

            SECTION("move")
            {
                // The patch
                json const patch = R"(
                    [
                        {"op": "move", "from": "/biscuits", "path": "/cookies"}
                    ]
                )"_json;

                // The result
                json result = R"(
                    {
                      "cookies": [
                        {"name": "Digestive"},
                        {"name": "Choco Liebniz"}
                      ]
                    }
                )"_json;

                // check if patched value is as expected
                CHECK(doc.patch(patch) == result);

                // check roundtrip
                CHECK(doc.patch(json::diff(doc, result)) == result);
            }

            SECTION("test")
            {
                // The patch
                json patch = R"(
                    [
                        {"op": "test", "path": "/best_biscuit/name", "value": "Choco Liebniz"}
                    ]
                )"_json;

                // the test will fail
                CHECK_THROWS_AS(doc.patch(patch), json::other_error&);
#if JSON_DIAGNOSTICS
                CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] (/0) unsuccessful: " + patch[0].dump());
#elif JSON_DIAGNOSTIC_POSITIONS
                CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] (bytes 47-117) unsuccessful: " + patch[0].dump());
#else
                CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] unsuccessful: " + patch[0].dump());
#endif
            }
        }
    }

    SECTION("Examples from bruth.github.io/jsonpatch-js")
    {
        SECTION("add")
        {
            CHECK(R"( {} )"_json.patch(
                      R"( [{"op": "add", "path": "/foo", "value": "bar"}] )"_json
                  ) == R"( {"foo": "bar"} )"_json);

            CHECK(R"( {"foo": [1, 3]} )"_json.patch(
                      R"( [{"op": "add", "path": "/foo", "value": "bar"}] )"_json
                  ) == R"( {"foo": "bar"} )"_json);

            CHECK(R"( {"foo": [{}]} )"_json.patch(
                      R"( [{"op": "add", "path": "/foo/0/bar", "value": "baz"}] )"_json
                  ) == R"( {"foo": [{"bar": "baz"}]} )"_json);
        }

        SECTION("remove")
        {
            CHECK(R"( {"foo": "bar"} )"_json.patch(
                      R"( [{"op": "remove", "path": "/foo"}] )"_json
                  ) == R"( {} )"_json);

            CHECK(R"( {"foo": [1, 2, 3]} )"_json.patch(
                      R"( [{"op": "remove", "path": "/foo/1"}] )"_json
                  ) == R"( {"foo": [1, 3]} )"_json);

            CHECK(R"( {"foo": [{"bar": "baz"}]} )"_json.patch(
                      R"( [{"op": "remove", "path": "/foo/0/bar"}] )"_json
                  ) == R"( {"foo": [{}]} )"_json);
        }

        SECTION("replace")
        {
            CHECK(R"( {"foo": "bar"} )"_json.patch(
                      R"( [{"op": "replace", "path": "/foo", "value": 1}] )"_json
                  ) == R"( {"foo": 1} )"_json);

            CHECK(R"( {"foo": [1, 2, 3]} )"_json.patch(
                      R"( [{"op": "replace", "path": "/foo/1", "value": 4}] )"_json
                  ) == R"( {"foo": [1, 4, 3]} )"_json);

            CHECK(R"( {"foo": [{"bar": "baz"}]} )"_json.patch(
                      R"( [{"op": "replace", "path": "/foo/0/bar", "value": 1}] )"_json
                  ) == R"( {"foo": [{"bar": 1}]} )"_json);
        }

        SECTION("move")
        {
            CHECK(R"( {"foo": [1, 2, 3]} )"_json.patch(
                      R"( [{"op": "move", "from": "/foo", "path": "/bar"}] )"_json
                  ) == R"( {"bar": [1, 2, 3]} )"_json);
        }

        SECTION("copy")
        {
            CHECK(R"( {"foo": [1, 2, 3]} )"_json.patch(
                      R"( [{"op": "copy", "from": "/foo/1", "path": "/bar"}] )"_json
                  ) == R"( {"foo": [1, 2, 3], "bar": 2} )"_json);
        }

        SECTION("copy")
        {
            CHECK_NOTHROW(R"( {"foo": "bar"} )"_json.patch(
                              R"( [{"op": "test", "path": "/foo", "value": "bar"}] )"_json));
        }
    }

    SECTION("Tests from github.com/json-patch/json-patch-tests")
    {
        for (const auto* filename :
                {
                    TEST_DATA_DIRECTORY "/json-patch-tests/spec_tests.json",
                    TEST_DATA_DIRECTORY "/json-patch-tests/tests.json"
                })
        {
            CAPTURE(filename)
            std::ifstream f(filename);
            json const suite = json::parse(f);

            for (const auto& test : suite)
            {
                INFO_WITH_TEMP(test.value("comment", ""));

                // skip tests marked as disabled
                if (test.value("disabled", false))
                {
                    continue;
                }

                const auto& doc = test["doc"];
                const auto& patch = test["patch"];

                if (test.count("error") == 0) // NOLINT(readability-container-contains)
                {
                    // if an expected value is given, use it; use doc otherwise
                    const auto& expected = test.value("expected", doc);
                    CHECK(doc.patch(patch) == expected);
                }
                else
                {
                    CHECK_THROWS(doc.patch(patch));
                }
            }
        }
    }
}

TEST_CASE("JSON patch - add to a primitive parent (regression #4292)")
{
    // Regression test for https://github.com/nlohmann/json/issues/4292
    //
    // An "add" operation whose parent location resolves to a primitive
    // (non-container) value must be rejected with a catchable exception.
    // Previously this hit JSON_ASSERT(false) in operation_add, which aborts
    // the process in debug builds and silently dropped the operation (leaving
    // a wrong result) when assertions were compiled out (NDEBUG). It now
    // throws out_of_range.411.
    //
    // The documents below are constructed programmatically (not parsed) so
    // they carry no byte positions; the JSON_DIAGNOSTICS path prefix is
    // handled by the guards. The exact message with positions is covered in
    // unit-diagnostic-positions.cpp.

    SECTION("string parent")
    {
        json const doc = {{"foo", {{"bar", "a string"}}}};
        json const patch = {{{"op", "add"}, {"path", "/foo/bar/baz"}, {"value", 1}}};
#if JSON_DIAGNOSTICS
        CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.411] (/foo/bar) cannot add value: the JSON Patch 'add' target's parent is of type string, but must be an object or array", json::out_of_range&);
#else
        CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.411] cannot add value: the JSON Patch 'add' target's parent is of type string, but must be an object or array", json::out_of_range&);
#endif
    }

    SECTION("number parent")
    {
        json const doc = {{"foo", 1}};
        json const patch = {{{"op", "add"}, {"path", "/foo/bar"}, {"value", 2}}};
#if JSON_DIAGNOSTICS
        CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.411] (/foo) cannot add value: the JSON Patch 'add' target's parent is of type number, but must be an object or array", json::out_of_range&);
#else
        CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.411] cannot add value: the JSON Patch 'add' target's parent is of type number, but must be an object or array", json::out_of_range&);
#endif
    }

    SECTION("original two-step sequence from the issue")
    {
        // The user's two-step patch from #4292: first turn /xyz/1 into a
        // string, then try to add a member inside that string.
        json const doc = R"( { "xyz": [ { "lmn": "214", "nnp": "001" } ] } )"_json;
        json const patch = R"(
            [
                { "op": "add", "path": "/xyz/1",     "value": "" },
                { "op": "add", "path": "/xyz/1/lmn", "value": "214" }
            ]
        )"_json;

        CHECK_THROWS_AS(doc.patch(patch), json::out_of_range&);
    }
}