From bed648ca552258fa7b1d3065c2fa612877fa0918 Mon Sep 17 00:00:00 2001 From: Raphael Grimm Date: Sun, 28 Aug 2022 13:59:07 +0200 Subject: [PATCH] Allow custom base class as node customization point (#3110) Co-authored-by: Niels Lohmann Co-authored-by: Florian Albrechtskirchinger Co-authored-by: barcode --- docs/examples/json_base_class_t.cpp | 88 +++++ docs/examples/json_base_class_t.output | 4 + docs/mkdocs/docs/api/basic_json/index.md | 4 +- .../docs/api/basic_json/json_base_class_t.md | 44 +++ docs/mkdocs/mkdocs.yml | 1 + .../detail/json_custom_base_class.hpp | 31 ++ include/nlohmann/detail/macro_scope.hpp | 5 +- include/nlohmann/json.hpp | 15 +- include/nlohmann/json_fwd.hpp | 3 +- single_include/nlohmann/json.hpp | 56 ++- single_include/nlohmann/json_fwd.hpp | 3 +- tests/src/unit-custom-base-class.cpp | 357 ++++++++++++++++++ 12 files changed, 595 insertions(+), 16 deletions(-) create mode 100644 docs/examples/json_base_class_t.cpp create mode 100644 docs/examples/json_base_class_t.output create mode 100644 docs/mkdocs/docs/api/basic_json/json_base_class_t.md create mode 100644 include/nlohmann/detail/json_custom_base_class.hpp create mode 100644 tests/src/unit-custom-base-class.cpp diff --git a/docs/examples/json_base_class_t.cpp b/docs/examples/json_base_class_t.cpp new file mode 100644 index 0000000000..d993522a70 --- /dev/null +++ b/docs/examples/json_base_class_t.cpp @@ -0,0 +1,88 @@ +#include +#include + +class visitor_adaptor_with_metadata +{ + public: + template + void visit(const Fnc& fnc) const; + + int metadata = 42; + private: + template + void do_visit(const Ptr& ptr, const Fnc& fnc) const; +}; + +using json = nlohmann::basic_json < + std::map, + std::vector, + std::string, + bool, + std::int64_t, + std::uint64_t, + double, + std::allocator, + nlohmann::adl_serializer, + std::vector, + visitor_adaptor_with_metadata + >; + +template +void visitor_adaptor_with_metadata::visit(const Fnc& fnc) const +{ + do_visit(json::json_pointer{}, fnc); +} + +template +void visitor_adaptor_with_metadata::do_visit(const Ptr& ptr, const Fnc& fnc) const +{ + using value_t = nlohmann::detail::value_t; + const json& j = *static_cast(this); + switch (j.type()) + { + case value_t::object: + fnc(ptr, j); + for (const auto& entry : j.items()) + { + entry.value().do_visit(ptr / entry.key(), fnc); + } + break; + case value_t::array: + fnc(ptr, j); + for (std::size_t i = 0; i < j.size(); ++i) + { + j.at(i).do_visit(ptr / std::to_string(i), fnc); + } + break; + case value_t::null: + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + fnc(ptr, j); + break; + case value_t::discarded: + default: + break; + } +} + +int main() +{ + // create a json object + json j; + j["null"]; + j["object"]["uint"] = 1U; + j["object"].metadata = 21; + + // visit and output + j.visit( + [&](const json::json_pointer & p, + const json & j) + { + std::cout << (p.empty() ? std::string{"/"} : p.to_string()) + << " - metadata = " << j.metadata << " -> " << j.dump() << '\n'; + }); +} diff --git a/docs/examples/json_base_class_t.output b/docs/examples/json_base_class_t.output new file mode 100644 index 0000000000..83ce1f693d --- /dev/null +++ b/docs/examples/json_base_class_t.output @@ -0,0 +1,4 @@ +/ - metadata = 42 -> {"null":null,"object":{"uint":1}} +/null - metadata = 42 -> null +/object - metadata = 21 -> {"uint":1} +/object/uint - metadata = 42 -> 1 diff --git a/docs/mkdocs/docs/api/basic_json/index.md b/docs/mkdocs/docs/api/basic_json/index.md index e474b662e9..fb2be8fefa 100644 --- a/docs/mkdocs/docs/api/basic_json/index.md +++ b/docs/mkdocs/docs/api/basic_json/index.md @@ -13,7 +13,8 @@ template< class NumberFloatType = double, template class AllocatorType = std::allocator, template class JSONSerializer = adl_serializer, - class BinaryType = std::vector + class BinaryType = std::vector > class basic_json; ``` @@ -32,6 +33,7 @@ class basic_json; | `AllocatorType` | type of the allocator to use | | | `JSONSerializer` | the serializer to resolve internal calls to `to_json()` and `from_json()` | [`json_serializer`](json_serializer.md) | | `BinaryType` | type for binary arrays | [`binary_t`](binary_t.md) | +| `CustomBaseClass` | extension point for user code | [`json_base_class_t`](json_base_class_t.md) | ## Specializations diff --git a/docs/mkdocs/docs/api/basic_json/json_base_class_t.md b/docs/mkdocs/docs/api/basic_json/json_base_class_t.md new file mode 100644 index 0000000000..0a939fc9b3 --- /dev/null +++ b/docs/mkdocs/docs/api/basic_json/json_base_class_t.md @@ -0,0 +1,44 @@ +# nlohmann::basic_json::json_base_class_t + +```cpp +using json_base_class_t = detail::json_base_class; +``` + +The base class used to inject custom functionality into each instance of `basic_json`. +Examples of such functionality might be metadata, additional member functions (e.g., visitors), or other application-specific code. + +## Template parameters + +`CustomBaseClass` +: the base class to be added to `basic_json` + +## Notes + +#### Default type + +The default value for `CustomBaseClass` is `void`. In this case an [empty base class](https://en.cppreference.com/w/cpp/language/ebo) is used and no additional functionality is injected. + +#### Limitations + +The type `CustomBaseClass` has to be a default-constructible class. +`basic_json` only supports copy/move construction/assignment if `CustomBaseClass` does so as well. + +## Examples + +??? example + + The following code shows how to inject custom data and methods for each node. + + ```cpp + --8<-- "examples/json_base_class_t.cpp" + ``` + + Output: + + ```json + --8<-- "examples/json_base_class_t.output" + ``` + +## Version history + +- Added in version 3.12.0. diff --git a/docs/mkdocs/mkdocs.yml b/docs/mkdocs/mkdocs.yml index 545584a922..823a59beae 100644 --- a/docs/mkdocs/mkdocs.yml +++ b/docs/mkdocs/mkdocs.yml @@ -141,6 +141,7 @@ nav: - 'is_string': api/basic_json/is_string.md - 'is_structured': api/basic_json/is_structured.md - 'items': api/basic_json/items.md + - 'json_base_class_t': api/basic_json/json_base_class_t.md - 'json_serializer': api/basic_json/json_serializer.md - 'max_size': api/basic_json/max_size.md - 'meta': api/basic_json/meta.md diff --git a/include/nlohmann/detail/json_custom_base_class.hpp b/include/nlohmann/detail/json_custom_base_class.hpp new file mode 100644 index 0000000000..ff06653aba --- /dev/null +++ b/include/nlohmann/detail/json_custom_base_class.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include // conditional, is_same + +#include + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/*! +@brief Default base class of the @ref basic_json class. + +So that the correct implementations of the copy / move ctors / assign operators +of @ref basic_json do not require complex case distinctions +(no base class / custom base class used as customization point), +@ref basic_json always has a base class. +By default, this class is used because it is empty and thus has no effect +on the behavior of @ref basic_json. +*/ +struct json_default_base {}; + +template +using json_base_class = typename std::conditional < + std::is_same::value, + json_default_base, + T + >::type; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END diff --git a/include/nlohmann/detail/macro_scope.hpp b/include/nlohmann/detail/macro_scope.hpp index 6248bea130..3ecf7a4c65 100644 --- a/include/nlohmann/detail/macro_scope.hpp +++ b/include/nlohmann/detail/macro_scope.hpp @@ -240,12 +240,13 @@ class NumberUnsignedType, class NumberFloatType, \ template class AllocatorType, \ template class JSONSerializer, \ - class BinaryType> + class BinaryType, \ + class CustomBaseClass> #define NLOHMANN_BASIC_JSON_TPL \ basic_json + AllocatorType, JSONSerializer, BinaryType, CustomBaseClass> // Macros to simplify conversion from/to types diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 6a2a84606d..c28726370a 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -93,6 +94,7 @@ The invariants are checked by member function assert_invariant(). */ NLOHMANN_BASIC_JSON_TPL_DECLARATION class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) + : public ::nlohmann::detail::json_base_class { private: template friend struct detail::external_constructor; @@ -119,6 +121,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// workaround type for MSVC using basic_json_t = NLOHMANN_BASIC_JSON_TPL; + using json_base_class_t = ::nlohmann::detail::json_base_class; JSON_PRIVATE_UNLESS_TESTED: // convenience aliases for types residing in namespace detail; @@ -1132,7 +1135,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @brief copy constructor /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ basic_json(const basic_json& other) - : m_type(other.m_type) + : json_base_class_t(other), + m_type(other.m_type) { // check of passed value is valid other.assert_invariant(); @@ -1200,11 +1204,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @brief move constructor /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ basic_json(basic_json&& other) noexcept - : m_type(std::move(other.m_type)), + : json_base_class_t(std::move(other)), + m_type(std::move(other.m_type)), m_value(std::move(other.m_value)) { // check that passed value is valid - other.assert_invariant(false); + other.assert_invariant(false); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) // invalidate payload other.m_type = value_t::null; @@ -1220,7 +1225,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec std::is_nothrow_move_constructible::value&& std::is_nothrow_move_assignable::value&& std::is_nothrow_move_constructible::value&& - std::is_nothrow_move_assignable::value + std::is_nothrow_move_assignable::value&& + std::is_nothrow_move_assignable::value ) { // check that passed value is valid @@ -1229,6 +1235,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec using std::swap; swap(m_type, other.m_type); swap(m_value, other.m_value); + json_base_class_t::operator=(std::move(other)); set_parents(); assert_invariant(); diff --git a/include/nlohmann/json_fwd.hpp b/include/nlohmann/json_fwd.hpp index 67172b14f5..af3ce7a29c 100644 --- a/include/nlohmann/json_fwd.hpp +++ b/include/nlohmann/json_fwd.hpp @@ -46,7 +46,8 @@ template class ObjectType = template class AllocatorType = std::allocator, template class JSONSerializer = adl_serializer, - class BinaryType = std::vector> + class BinaryType = std::vector, // cppcheck-suppress syntaxError + class CustomBaseClass = void> class basic_json; /// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index a7c12a3e1c..83705f9f83 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -2590,12 +2590,13 @@ JSON_HEDLEY_DIAGNOSTIC_POP class NumberUnsignedType, class NumberFloatType, \ template class AllocatorType, \ template class JSONSerializer, \ - class BinaryType> + class BinaryType, \ + class CustomBaseClass> #define NLOHMANN_BASIC_JSON_TPL \ basic_json + AllocatorType, JSONSerializer, BinaryType, CustomBaseClass> // Macros to simplify conversion from/to types @@ -3389,7 +3390,8 @@ NLOHMANN_JSON_NAMESPACE_END template class AllocatorType = std::allocator, template class JSONSerializer = adl_serializer, - class BinaryType = std::vector> + class BinaryType = std::vector, // cppcheck-suppress syntaxError + class CustomBaseClass = void> class basic_json; /// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document @@ -13673,6 +13675,40 @@ NLOHMANN_JSON_NAMESPACE_END // #include +// #include + + +#include // conditional, is_same + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/*! +@brief Default base class of the @ref basic_json class. + +So that the correct implementations of the copy / move ctors / assign operators +of @ref basic_json do not require complex case distinctions +(no base class / custom base class used as customization point), +@ref basic_json always has a base class. +By default, this class is used because it is empty and thus has no effect +on the behavior of @ref basic_json. +*/ +struct json_default_base {}; + +template +using json_base_class = typename std::conditional < + std::is_same::value, + json_default_base, + T + >::type; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ @@ -19271,6 +19307,7 @@ The invariants are checked by member function assert_invariant(). */ NLOHMANN_BASIC_JSON_TPL_DECLARATION class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) + : public ::nlohmann::detail::json_base_class { private: template friend struct detail::external_constructor; @@ -19297,6 +19334,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// workaround type for MSVC using basic_json_t = NLOHMANN_BASIC_JSON_TPL; + using json_base_class_t = ::nlohmann::detail::json_base_class; JSON_PRIVATE_UNLESS_TESTED: // convenience aliases for types residing in namespace detail; @@ -20310,7 +20348,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @brief copy constructor /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ basic_json(const basic_json& other) - : m_type(other.m_type) + : json_base_class_t(other), + m_type(other.m_type) { // check of passed value is valid other.assert_invariant(); @@ -20378,11 +20417,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @brief move constructor /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ basic_json(basic_json&& other) noexcept - : m_type(std::move(other.m_type)), + : json_base_class_t(std::move(other)), + m_type(std::move(other.m_type)), m_value(std::move(other.m_value)) { // check that passed value is valid - other.assert_invariant(false); + other.assert_invariant(false); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) // invalidate payload other.m_type = value_t::null; @@ -20398,7 +20438,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec std::is_nothrow_move_constructible::value&& std::is_nothrow_move_assignable::value&& std::is_nothrow_move_constructible::value&& - std::is_nothrow_move_assignable::value + std::is_nothrow_move_assignable::value&& + std::is_nothrow_move_assignable::value ) { // check that passed value is valid @@ -20407,6 +20448,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec using std::swap; swap(m_type, other.m_type); swap(m_value, other.m_value); + json_base_class_t::operator=(std::move(other)); set_parents(); assert_invariant(); diff --git a/single_include/nlohmann/json_fwd.hpp b/single_include/nlohmann/json_fwd.hpp index 83c21f857b..98070dfc95 100644 --- a/single_include/nlohmann/json_fwd.hpp +++ b/single_include/nlohmann/json_fwd.hpp @@ -147,7 +147,8 @@ template class ObjectType = template class AllocatorType = std::allocator, template class JSONSerializer = adl_serializer, - class BinaryType = std::vector> + class BinaryType = std::vector, // cppcheck-suppress syntaxError + class CustomBaseClass = void> class basic_json; /// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document diff --git a/tests/src/unit-custom-base-class.cpp b/tests/src/unit-custom-base-class.cpp new file mode 100644 index 0000000000..1803bd864f --- /dev/null +++ b/tests/src/unit-custom-base-class.cpp @@ -0,0 +1,357 @@ +/* + __ _____ _____ _____ + __| | __| | | | JSON for Modern C++ (test suite) +| | |__ | | | | | | version 3.10.2 +|_____|_____|_____|_|___| https://github.com/nlohmann/json + +Licensed under the MIT License . +SPDX-License-Identifier: MIT +Copyright (c) 2013-2019 Niels Lohmann . + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include +#include +#include + +#include "doctest_compatibility.h" + +#include + +// Test extending nlohmann::json by using a custom base class. +// Add some metadata to each node and test the behaviour of copy / move +template +class json_metadata +{ + public: + using metadata_t = MetaDataType; + metadata_t& metadata() + { + return m_metadata; + } + const metadata_t& metadata() const + { + return m_metadata; + } + private: + metadata_t m_metadata = {}; +}; + +template +using json_with_metadata = + nlohmann::basic_json < + std::map, + std::vector, + std::string, + bool, + std::int64_t, + std::uint64_t, + double, + std::allocator, + nlohmann::adl_serializer, + std::vector, + json_metadata + >; + +TEST_CASE("JSON Node Metadata") +{ + SECTION("type int") + { + using json = json_with_metadata; + json null; + auto obj = json::object(); + auto array = json::array(); + + null.metadata() = 1; + obj.metadata() = 2; + array.metadata() = 3; + auto copy = array; + + CHECK(null.metadata() == 1); + CHECK(obj.metadata() == 2); + CHECK(array.metadata() == 3); + CHECK(copy.metadata() == 3); + } + SECTION("type vector") + { + using json = json_with_metadata>; + json value; + value.metadata().emplace_back(1); + auto copy = value; + value.metadata().emplace_back(2); + + CHECK(copy.metadata().size() == 1); + CHECK(copy.metadata().at(0) == 1); + CHECK(value.metadata().size() == 2); + CHECK(value.metadata().at(0) == 1); + CHECK(value.metadata().at(1) == 2); + } + SECTION("copy ctor") + { + using json = json_with_metadata>; + json value; + value.metadata().emplace_back(1); + value.metadata().emplace_back(2); + + json copy = value; + + CHECK(copy.metadata().size() == 2); + CHECK(copy.metadata().at(0) == 1); + CHECK(copy.metadata().at(1) == 2); + CHECK(value.metadata().size() == 2); + CHECK(value.metadata().at(0) == 1); + CHECK(value.metadata().at(1) == 2); + + value.metadata().clear(); + CHECK(copy.metadata().size() == 2); + CHECK(value.metadata().size() == 0); + } + SECTION("move ctor") + { + using json = json_with_metadata>; + json value; + value.metadata().emplace_back(1); + value.metadata().emplace_back(2); + + const json moved = std::move(value); + + CHECK(moved.metadata().size() == 2); + CHECK(moved.metadata().at(0) == 1); + CHECK(moved.metadata().at(1) == 2); + } + SECTION("move assign") + { + using json = json_with_metadata>; + json value; + value.metadata().emplace_back(1); + value.metadata().emplace_back(2); + + json moved; + moved = std::move(value); + + CHECK(moved.metadata().size() == 2); + CHECK(moved.metadata().at(0) == 1); + CHECK(moved.metadata().at(1) == 2); + } + SECTION("copy assign") + { + using json = json_with_metadata>; + json value; + value.metadata().emplace_back(1); + value.metadata().emplace_back(2); + + json copy; + copy = value; + + CHECK(copy.metadata().size() == 2); + CHECK(copy.metadata().at(0) == 1); + CHECK(copy.metadata().at(1) == 2); + CHECK(value.metadata().size() == 2); + CHECK(value.metadata().at(0) == 1); + CHECK(value.metadata().at(1) == 2); + + value.metadata().clear(); + CHECK(copy.metadata().size() == 2); + CHECK(value.metadata().size() == 0); + } + SECTION("type unique_ptr") + { + using json = json_with_metadata>; + json value; + value.metadata().reset(new int(42)); // NOLINT(cppcoreguidelines-owning-memory) + auto moved = std::move(value); + + CHECK(moved.metadata() != nullptr); + CHECK(*moved.metadata() == 42); + } + SECTION("type vector in json array") + { + using json = json_with_metadata>; + json value; + value.metadata().emplace_back(1); + value.metadata().emplace_back(2); + + json array(10, value); + + CHECK(value.metadata().size() == 2); + CHECK(value.metadata().at(0) == 1); + CHECK(value.metadata().at(1) == 2); + + for (const auto& val : array) + { + CHECK(val.metadata().size() == 2); + CHECK(val.metadata().at(0) == 1); + CHECK(val.metadata().at(1) == 2); + } + } +} + +// Test extending nlohmann::json by using a custom base class. +// Add a custom member function template iterating over the whole json tree. +class visitor_adaptor +{ + public: + template + void visit(const Fnc& fnc) const; + private: + template + void do_visit(const Ptr& ptr, const Fnc& fnc) const; +}; + +using json_with_visitor_t = nlohmann::basic_json < + std::map, + std::vector, + std::string, + bool, + std::int64_t, + std::uint64_t, + double, + std::allocator, + nlohmann::adl_serializer, + std::vector, + visitor_adaptor + >; + + +template +void visitor_adaptor::visit(const Fnc& fnc) const +{ + do_visit(json_with_visitor_t::json_pointer{}, fnc); +} + +template +void visitor_adaptor::do_visit(const Ptr& ptr, const Fnc& fnc) const +{ + using value_t = nlohmann::detail::value_t; + const json_with_visitor_t& json = *static_cast(this); + switch (json.type()) + { + case value_t::object: + for (const auto& entry : json.items()) + { + entry.value().do_visit(ptr / entry.key(), fnc); + } + break; + case value_t::array: + for (std::size_t i = 0; i < json.size(); ++i) + { + json.at(i).do_visit(ptr / std::to_string(i), fnc); + } + break; + case value_t::discarded: + break; + case value_t::null: + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + default: + fnc(ptr, json); + } +} + +TEST_CASE("JSON Visit Node") +{ + json_with_visitor_t json; + json["null"]; + json["int"] = -1; + json["uint"] = 1U; + json["float"] = 1.0; + json["boolean"] = true; + json["string"] = "string"; + json["array"].push_back(0); + json["array"].push_back(1); + json["array"].push_back(json); + + std::set expected + { + "/null - null - null", + "/int - number_integer - -1", + "/uint - number_unsigned - 1", + "/float - number_float - 1.0", + "/boolean - boolean - true", + "/string - string - \"string\"", + "/array/0 - number_integer - 0", + "/array/1 - number_integer - 1", + + "/array/2/null - null - null", + "/array/2/int - number_integer - -1", + "/array/2/uint - number_unsigned - 1", + "/array/2/float - number_float - 1.0", + "/array/2/boolean - boolean - true", + "/array/2/string - string - \"string\"", + "/array/2/array/0 - number_integer - 0", + "/array/2/array/1 - number_integer - 1" + }; + + json.visit( + [&](const json_with_visitor_t::json_pointer & p, + const json_with_visitor_t& j) + { + std::stringstream str; + str << p.to_string() << " - " ; + using value_t = nlohmann::detail::value_t; + switch (j.type()) + { + case value_t::object: + str << "object"; + break; + case value_t::array: + str << "array"; + break; + case value_t::discarded: + str << "discarded"; + break; + case value_t::null: + str << "null"; + break; + case value_t::string: + str << "string"; + break; + case value_t::boolean: + str << "boolean"; + break; + case value_t::number_integer: + str << "number_integer"; + break; + case value_t::number_unsigned: + str << "number_unsigned"; + break; + case value_t::number_float: + str << "number_float"; + break; + case value_t::binary: + str << "binary"; + break; + default: + str << "error"; + break; + } + str << " - " << j.dump(); + CHECK(json.at(p) == j); + INFO(str.str()); + CHECK(expected.count(str.str()) == 1); + expected.erase(str.str()); + } + ); + CHECK(expected.empty()); +}