// __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ (supporting code) // | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // // SPDX-FileCopyrightText: 2013-2026 Niels Lohmann // SPDX-License-Identifier: MIT #include "doctest_compatibility.h" #include using nlohmann::json; using nlohmann::json_document; using nlohmann::json_view; using nlohmann::json_editable_document; using nlohmann::ordered_json_document; #include "json_view_test_helpers.hpp" #if !defined(JSON_NOEXCEPTION) using json_view_test::exception_of_call; #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef JSON_HAS_CPP_17 #include #endif // The from_json() functions of the NLOHMANN_DEFINE_TYPE_* and // NLOHMANN_JSON_SERIALIZE_ENUM macros read views directly; other from_json() // functions get the value of materialize(). namespace view_from_json { struct point { int x = 0; int y = 0; NLOHMANN_DEFINE_TYPE_INTRUSIVE(point, x, y) friend bool operator==(const point& a, const point& b) { return a.x == b.x && a.y == b.y; } }; struct shape { std::string name; std::vector points; point center; std::map named; NLOHMANN_DEFINE_TYPE_INTRUSIVE(shape, name, points, center, named) }; struct with_defaults { int a = 1; std::string b = "default"; std::vector c = {1, 2}; }; NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(with_defaults, a, b, c) struct with_names { int value = 0; std::string text; }; NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_NAMES(with_names, "Value", value, "Text", text) struct empty { NLOHMANN_DEFINE_TYPE_INTRUSIVE(empty) }; enum class color { red, green, blue }; NLOHMANN_JSON_SERIALIZE_ENUM(color, { {color::red, "red"}, {color::green, "green"}, {color::blue, nullptr}, }) enum class strict_color { red, green }; NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(strict_color, { {strict_color::red, "red"}, {strict_color::green, 1}, }) struct base { int id = 0; NLOHMANN_DEFINE_TYPE_INTRUSIVE(base, id) }; struct derived : base { color c = color::red; NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE(derived, base, c) }; struct derived_with_defaults : base { std::string label = "none"; NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT(derived_with_defaults, base, label) }; // a base whose from_json() is written for json only: the derived type reads // its own members from the view and the base part from materialize() struct legacy_base { int id = 0; }; // NOLINTNEXTLINE(misc-use-internal-linkage): found by ADL, like the from_json() functions of users inline void from_json(const json& j, legacy_base& b) { b.id = j.at("id").get(); } struct derived_from_legacy : legacy_base { int extra = 0; }; NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE(derived_from_legacy, legacy_base, extra) // from_json() for json only: converted through materialize() struct legacy { int v = 0; }; // NOLINTNEXTLINE(misc-use-internal-linkage): found by ADL, like the from_json() functions of users inline void from_json(const json& j, legacy& l) { l.v = j.at("v").get(); } // an unconstrained from_json() template, as in the documentation of // arbitrary type conversions: converted through materialize() struct unconstrained { int v = 0; }; template // NOLINTNEXTLINE(misc-use-internal-linkage): found by ADL, like the from_json() functions of users void from_json(const BasicJsonType& j, unconstrained& u) { u.v = j.at("v").template get(); } // a from_json() constrained by the user to views and json: reads the view struct constrained { int v = 0; }; template < typename J, typename std::enable_if < nlohmann::is_json_view::value || std::is_same::value, int >::type = 0 > // NOLINTNEXTLINE(misc-use-internal-linkage): found by ADL, like the from_json() functions of users void from_json(const J& j, constrained& c) { c.v = j.at("v").template get(); } } // namespace view_from_json namespace { template struct reads_view : nlohmann::detail::has_view_from_json, View, T> {}; // whether a member converts to the same T from the view and from the materialized value template bool same(const json_view& v, const json& j, const char* key) { return v[key].get() == j[key].get(); } } // namespace TEST_CASE("json_view from_json") { using namespace view_from_json; // NOLINT(google-build-using-namespace) SECTION("which from_json() functions read views") { static_assert(reads_view::value, "macro types read views"); static_assert(reads_view::value, "macro types read views"); static_assert(reads_view::value, "macro types read views"); static_assert(reads_view::value, "macro types read views"); static_assert(reads_view::value, "macro types read views"); static_assert(reads_view::value, "macro types read views"); static_assert(reads_view::value, "macro types read views"); static_assert(reads_view::value, "enum macro types read views"); static_assert(reads_view::value, "enum macro types read views"); static_assert(reads_view::value, "constrained from_json() reads views"); static_assert(reads_view::value, "editable views as well"); static_assert(reads_view::value, "views of ordered_json as well"); static_assert(!reads_view::value, "from_json() for json"); static_assert(!reads_view::value, "from_json() for json"); static_assert(!reads_view::value, "unconstrained from_json()"); static_assert(nlohmann::is_json_view::value, "is_json_view"); static_assert(nlohmann::is_json_view::value, "is_json_view"); static_assert(!nlohmann::is_json_view::value, "is_json_view"); static_assert(!nlohmann::is_json_view::value, "is_json_view"); static_assert(!nlohmann::is_json_view::value, "is_json_view"); } SECTION("the library's own conversions read views") { static_assert(reads_view>::value, "std types"); static_assert(reads_view>::value, "std types"); static_assert(reads_view>::value, "std types"); static_assert(reads_view>::value, "std types"); static_assert(reads_view>::value, "std types"); static_assert(reads_view>::value, "std types"); static_assert(reads_view>::value, "std types"); static_assert(reads_view>::value, "std types"); static_assert(reads_view>::value, "std types"); static_assert(reads_view>::value, "std types"); static_assert(reads_view>::value, "std types"); static_assert(reads_view>::value, "std types"); static_assert(reads_view>::value, "std types"); static_assert(reads_view>::value, "std types"); static_assert(reads_view>::value, "std types"); static_assert(reads_view>::value, "std types"); static_assert(reads_view::value, "std types"); static_assert(reads_view::value, "std types"); static_assert(reads_view::value, "std types"); static_assert(reads_view::value, "std types"); static_assert(reads_view::value, "std types"); static_assert(!reads_view::value, "get() is materialize()"); static_assert(reads_view::value, "std types"); #ifdef JSON_HAS_CPP_17 static_assert(reads_view>::value, "std types"); #endif CHECK(true); } SECTION("std types: the same values as from the materialized value") { const json_document d = json_document::parse(R"({"ints": [3, 1, 2, 1], "strings": ["b", "a", "b"], "pairs": [[1, "x"], [2, "y"]], "points": [{"x": 1, "y": 2}, {"x": 3, "y": 4}], "object": {"b": 1, "a": 2}, "triple": [1, true, {"x": 5, "y": 6}], "colors": {"green": 1, "red": 2}, "bytes": [1, 2, 255], "nested": [[1, 2], [3]], "number": 1.5, "flag": true, "text": "hello", "nothing": null, "mixed": [1, 2.5, -3, 4], "flags": [true, false, true]})"); const json j = d.root().materialize(); const json_view v = d.root(); CHECK(same>(v, j, "ints")); CHECK(same>(v, j, "ints")); CHECK(same>(v, j, "ints")); CHECK(same>(v, j, "ints")); CHECK(same>(v, j, "strings")); CHECK(same>(v, j, "ints")); CHECK(same>(v, j, "ints")); CHECK(same>(v, j, "mixed")); CHECK((v["pairs"][0].get>() == j["pairs"][0].get>())); CHECK(same>>(v, j, "pairs")); CHECK(same>(v, j, "triple")); CHECK(same>(v, j, "pairs")); CHECK(same>(v, j, "pairs")); CHECK(same>(v, j, "object")); CHECK(same>(v, j, "object")); CHECK(same>(v, j, "colors")); CHECK(same>(v, j, "points")); CHECK(same>(v, j, "ints")); CHECK(same>(v, j, "flags")); CHECK(same>(v, j, "bytes")); CHECK(same>>(v, j, "nested")); CHECK(same>(v, j, "mixed")); CHECK(same(v, j, "number")); CHECK(same(v, j, "flag")); CHECK(same(v, j, "text")); CHECK(same(v, j, "nothing")); CHECK(same(v, j, "object")); const std::valarray va = v["ints"].get>(); CHECK(va.size() == 4); CHECK(va[0] == 3); int c_array[2][2] = {{0, 0}, {0, 0}}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays): the conversion to C arrays v["nested"][0].get_to(c_array[0]); CHECK(c_array[0][1] == 2); #ifdef JSON_HAS_CPP_17 CHECK(v["points"][0].get>()->y == 2); CHECK(!v["nothing"].get>().has_value()); #endif // containers of views keep views of the elements const std::vector views = v["points"].get>(); CHECK(views.size() == 2); CHECK(views[1]["x"].get() == 3); CHECK(v["object"].get>().at("a").get() == 2); } SECTION("std types: duplicate keys keep the last value, as from the materialized value") { const json_document d = json_document::parse(R"({"a": 1, "b": 2, "a": 3})"); CHECK((d.root().get>() == d.root().materialize().get>())); CHECK(d.root().get>().at("a") == 3); CHECK(d.root().get>().at("a") == 3); } #if !defined(JSON_NOEXCEPTION) && !JSON_DIAGNOSTICS SECTION("std types: the same errors as from the materialized value") { const json_document d = json_document::parse(R"([[1, "x"], {"a": 1}, "text", [1], 5, null])"); const json j = d.root().materialize(); for (std::size_t i = 0; i < 6; ++i) { CAPTURE(i) const json_view v = d.root()[i]; CHECK(exception_of_call([&] { static_cast(v.get>()); }) == exception_of_call([&] { static_cast(j[i].get>()); })); CHECK(exception_of_call([&] { static_cast(v.get>()); }) == exception_of_call([&] { static_cast(j[i].get>()); })); CHECK(exception_of_call([&] { static_cast(v.get>()); }) == exception_of_call([&] { static_cast(j[i].get>()); })); CHECK(exception_of_call([&] { static_cast(v.get>()); }) == exception_of_call([&] { static_cast(j[i].get>()); })); CHECK(exception_of_call([&] { static_cast(v.get>()); }) == exception_of_call([&] { static_cast(j[i].get>()); })); CHECK(exception_of_call([&] { static_cast(v.get>()); }) == exception_of_call([&] { static_cast(j[i].get>()); })); CHECK(exception_of_call([&] { static_cast(v.get>()); }) == exception_of_call([&] { static_cast(j[i].get>()); })); CHECK(exception_of_call([&] { static_cast(v.get()); }) == exception_of_call([&] { static_cast(j[i].get()); })); CHECK(exception_of_call([&] { static_cast(v.get>()); }) == exception_of_call([&] { static_cast(j[i].get>()); })); } } #endif SECTION("macro types") { const json_document d = json_document::parse(R"({"name": "s", "points": [{"x": 1, "y": 2}, {"x": 3, "y": 4}], "center": {"x": 5, "y": 6}, "named": {"a": {"x": 7, "y": 8}}})"); const shape s = d.root().get(); CHECK(s.name == "s"); CHECK(s.points.size() == 2); CHECK(s.points[1].y == 4); CHECK(s.center.x == 5); CHECK(s.named.at("a").y == 8); point p; d.root()["center"].get_to(p); CHECK(p.x == 5); CHECK(p.y == 6); static_cast(d.root()["center"].get()); } SECTION("defaults and names") { const json_document d = json_document::parse(R"({"a": 5, "Value": 3, "Text": "t"})"); const with_defaults w = d.root().get(); CHECK(w.a == 5); CHECK(w.b == "default"); CHECK(w.c == std::vector {1, 2}); const json_document null_document = json_document::parse("null"); CHECK(null_document.root().get().a == 1); const with_names n = d.root().get(); CHECK(n.value == 3); CHECK(n.text == "t"); } SECTION("enums") { const json_document d = json_document::parse(R"(["green", null, "purple", 1, "red"])"); CHECK(d.root()[0].get() == color::green); CHECK(d.root()[1].get() == color::blue); CHECK(d.root()[2].get() == color::red); // unknown values map to the first entry CHECK(d.root()[3].get() == strict_color::green); CHECK(d.root()[4].get() == strict_color::red); #if !defined(JSON_NOEXCEPTION) const json j = d.root().materialize(); CHECK(exception_of_call([&] { static_cast(d.root()[2].get()); }) == "[json.exception.out_of_range.410] enum value out of range for strict_color: \"purple\""); #if !JSON_DIAGNOSTICS CHECK(exception_of_call([&] { static_cast(d.root()[2].get()); }) == exception_of_call([&] { static_cast(j[2].get()); })); #endif #endif } SECTION("derived types") { const json_document d = json_document::parse(R"({"id": 7, "c": "green", "extra": 3})"); const derived x = d.root().get(); CHECK(x.id == 7); CHECK(x.c == color::green); const derived_with_defaults y = d.root().get(); CHECK(y.id == 7); CHECK(y.label == "none"); const derived_from_legacy z = d.root().get(); CHECK(z.id == 7); CHECK(z.extra == 3); } SECTION("other from_json() functions get the value of materialize()") { const json_document d = json_document::parse(R"({"v": 4})"); CHECK(d.root().get().v == 4); CHECK(d.root().get().v == 4); CHECK(d.root().get().v == 4); legacy l; d.root().get_to(l); CHECK(l.v == 4); } SECTION("duplicate keys: members are looked up in the view, where the first one wins") { const json_document d = json_document::parse(R"({"x": 1, "y": 2, "x": 3})"); CHECK(d.root().get().x == 1); // materialize() keeps the last value, like parse() CHECK(d.root().materialize().get().x == 3); } SECTION("editable documents and ordered_json") { json_editable_document e = json_editable_document::parse(R"({"x": 1, "y": 2})"); e.set(e.root(), "x", 10); CHECK(e.root().get().x == 10); const ordered_json_document o = ordered_json_document::parse(R"({"name": "o", "points": [], "center": {"x": 1, "y": 2}, "named": {}})"); CHECK(o.root().get().center.y == 2); } #if !defined(JSON_NOEXCEPTION) && !JSON_DIAGNOSTICS SECTION("errors are those of the same conversion of a json value") { // (with JSON_DIAGNOSTICS, those of json name the path of the value) const json_document d = json_document::parse(R"([{"x": 1}, {"x": "1", "y": 2}, [1, 2], {"id": "7", "c": "red"}])"); const json j = d.root().materialize(); for (std::size_t i = 0; i < 3; ++i) { CAPTURE(i) CHECK(exception_of_call([&] { static_cast(d.root()[i].get()); }) == exception_of_call([&] { static_cast(j[i].get()); })); } CHECK(exception_of_call([&] { static_cast(d.root()[3].get()); }) == exception_of_call([&] { static_cast(j[3].get()); })); } #endif }