diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 81fac6d94..3e6258d43 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -52,6 +52,10 @@ There are currently two files which need to be edited: Please understand that I cannot accept pull requests changing only file `src/json.hpp`. +## Note + +- If you open a pull request, the code will be automatically tested with [Valgrind](http://valgrind.org)'s Memcheck tool to detect memory leaks. Please be aware that the execution with Valgrind _may_ in rare cases yield different behavior than running the code directly. This can result in failing unit tests which run successfully without Valgrind. + ## Please don't - Only make changes to file `src/json.hpp` -- please read the paragraph above and understand why `src/json.hpp.re2c` exists. @@ -67,3 +71,4 @@ The following areas really need contribution: - Extending the **continuous integration** beyond Linux running some versions of GCC and Clang on [Travis](https://travis-ci.org/nlohmann/json) and Microsoft Visual Studio on [AppVeyor](https://ci.appveyor.com/project/nlohmann/json). We have found a lot of bugs just because several compilers behave in a slightly different manner. - Improving the efficiency of the **JSON parser**. The current parser is implemented as a naive recursive descent parser with hand coded string handling. More sophisticated approaches like LALR parsers would be really appreciated. That said, parser generators like Bison or ANTLR do not play nice with single-header files -- I really would like to keep the parser inside the `json.hpp` header, and I am not aware of approaches similar to [`re2c`](http://re2c.org) for parsing. - Extending and updating existing **benchmarks** to include (the most recent version of) this library. Though efficiency is not everything, speed and memory consumption are very important characteristics for C++ developers, so having proper comparisons would be interesting. +- Check the code with [Coverity](https://scan.coverity.com). diff --git a/README.md b/README.md index 95e920f1b..68d4a1fb8 100644 --- a/README.md +++ b/README.md @@ -382,7 +382,7 @@ I deeply appreciate the help of the following people. - [Colin Hirsch](https://github.com/ColinH) took care of a small namespace issue. - [Huu Nguyen](https://github.com/whoshuu) correct a variable name in the documentation. - [Silverweed](https://github.com/silverweed) overloaded `parse()` to accept an rvalue reference. -- [dariomt](https://github.com/dariomt) fixed a subtlety in MSVC type support. +- [dariomt](https://github.com/dariomt) fixed a subtlety in MSVC type support and implemented the `get_ref()` function to get a reference to stored values. - [ZahlGraf](https://github.com/ZahlGraf) added a workaround that allows compilation using Android NDK. - [whackashoe](https://github.com/whackashoe) replaced a function that was marked as unsafe by Visual Studio. - [406345](https://github.com/406345) fixed two small warnings. diff --git a/doc/examples/get_ref.cpp b/doc/examples/get_ref.cpp new file mode 100644 index 000000000..c73cff3e4 --- /dev/null +++ b/doc/examples/get_ref.cpp @@ -0,0 +1,26 @@ +#include + +using namespace nlohmann; + +int main() +{ + // create a JSON number + json value = 17; + + // explicitly getting references + auto r1 = value.get_ref(); + auto r2 = value.get_ref(); + + // print the values + std::cout << r1 << ' ' << r2 << '\n'; + + // incompatible type throws exception + try + { + auto r3 = value.get_ref(); + } + catch (std::domain_error& ex) + { + std::cout << ex.what() << '\n'; + } +} diff --git a/doc/examples/get_ref.link b/doc/examples/get_ref.link new file mode 100644 index 000000000..4c8fd69af --- /dev/null +++ b/doc/examples/get_ref.link @@ -0,0 +1 @@ +online \ No newline at end of file diff --git a/doc/examples/get_ref.output b/doc/examples/get_ref.output new file mode 100644 index 000000000..50bc0df69 --- /dev/null +++ b/doc/examples/get_ref.output @@ -0,0 +1,2 @@ +17 17 +incompatible ReferenceType for get_ref, actual type is number diff --git a/src/json.hpp b/src/json.hpp index 27950c0fb..55c346471 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -2626,6 +2626,35 @@ class basic_json return is_number_float() ? &m_value.number_float : nullptr; } + /*! + @brief helper function to implement get_ref() + + This funcion helps to implement get_ref() without code duplication for + const and non-const overloads + + @tparam ThisType will be deduced as `basic_json` or `const basic_json` + + @throw std::domain_error if ReferenceType does not match underlying value + type of the current JSON + */ + template + static ReferenceType get_ref_impl(ThisType& obj) + { + // delegate the call to get_ptr<>() + using PointerType = typename std::add_pointer::type; + auto ptr = obj.template get_ptr(); + + if (ptr != nullptr) + { + return *ptr; + } + else + { + throw std::domain_error("incompatible ReferenceType for get_ref, actual type is " + + obj.type_name()); + } + } + public: /// @name value access @@ -2773,6 +2802,57 @@ class basic_json return get_impl_ptr(static_cast(nullptr)); } + /*! + @brief get a reference value (implicit) + + Implict reference access to the internally stored JSON value. No copies are + made. + + @warning Writing data to the referee of the result yields an undefined + state. + + @tparam ReferenceType reference type; must be a reference to @ref array_t, + @ref object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, or + @ref number_float_t. + + @return reference to the internally stored JSON value if the requested + reference type @a ReferenceType fits to the JSON value; throws + std::domain_error otherwise + + @throw std::domain_error in case passed type @a ReferenceType is + incompatible with the stored JSON value + + @complexity Constant. + + @liveexample{The example shows several calls to `get_ref()`.,get_ref} + + @since version 1.0.1 + */ + template::value + , int>::type = 0> + ReferenceType get_ref() + { + // delegate call to get_ref_impl + return get_ref_impl(*this); + } + + /*! + @brief get a reference value (implicit) + @copydoc get_ref() + */ + template::value + and std::is_const::type>::value + , int>::type = 0> + ReferenceType get_ref() const + { + // delegate call to get_ref_impl + return get_ref_impl(*this); + } + /*! @brief get a value (implicit) diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c index 6834e56d5..7a906f8b5 100644 --- a/src/json.hpp.re2c +++ b/src/json.hpp.re2c @@ -2626,6 +2626,35 @@ class basic_json return is_number_float() ? &m_value.number_float : nullptr; } + /*! + @brief helper function to implement get_ref() + + This funcion helps to implement get_ref() without code duplication for + const and non-const overloads + + @tparam ThisType will be deduced as `basic_json` or `const basic_json` + + @throw std::domain_error if ReferenceType does not match underlying value + type of the current JSON + */ + template + static ReferenceType get_ref_impl(ThisType& obj) + { + // delegate the call to get_ptr<>() + using PointerType = typename std::add_pointer::type; + auto ptr = obj.template get_ptr(); + + if (ptr != nullptr) + { + return *ptr; + } + else + { + throw std::domain_error("incompatible ReferenceType for get_ref, actual type is " + + obj.type_name()); + } + } + public: /// @name value access @@ -2773,6 +2802,57 @@ class basic_json return get_impl_ptr(static_cast(nullptr)); } + /*! + @brief get a reference value (implicit) + + Implict reference access to the internally stored JSON value. No copies are + made. + + @warning Writing data to the referee of the result yields an undefined + state. + + @tparam ReferenceType reference type; must be a reference to @ref array_t, + @ref object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, or + @ref number_float_t. + + @return reference to the internally stored JSON value if the requested + reference type @a ReferenceType fits to the JSON value; throws + std::domain_error otherwise + + @throw std::domain_error in case passed type @a ReferenceType is + incompatible with the stored JSON value + + @complexity Constant. + + @liveexample{The example shows several calls to `get_ref()`.,get_ref} + + @since version 1.0.1 + */ + template::value + , int>::type = 0> + ReferenceType get_ref() + { + // delegate call to get_ref_impl + return get_ref_impl(*this); + } + + /*! + @brief get a reference value (implicit) + @copydoc get_ref() + */ + template::value + and std::is_const::type>::value + , int>::type = 0> + ReferenceType get_ref() const + { + // delegate call to get_ref_impl + return get_ref_impl(*this); + } + /*! @brief get a value (implicit) diff --git a/test/unit.cpp b/test/unit.cpp index d5d19396e..374ad9baf 100644 --- a/test/unit.cpp +++ b/test/unit.cpp @@ -2763,6 +2763,22 @@ TEST_CASE("pointer access") CHECK(value.get_ptr() == nullptr); } + SECTION("pointer access to const object_t") + { + using test_type = json::object_t; + const json value = {{"one", 1}, {"two", 2}}; + + // this should not compile + // test_type* p1 = value.get_ptr(); + + // check if pointers are returned correctly + const test_type* p2 = value.get_ptr(); + CHECK(*p2 == value.get()); + + const test_type* const p3 = value.get_ptr(); + CHECK(p2 == p3); + } + SECTION("pointer access to array_t") { using test_type = json::array_t; @@ -2932,6 +2948,176 @@ TEST_CASE("pointer access") } } +TEST_CASE("reference access") +{ + // create a JSON value with different types + json json_types = + { + {"boolean", true}, + { + "number", { + {"integer", 42}, + {"floating-point", 17.23} + } + }, + {"string", "Hello, world!"}, + {"array", {1, 2, 3, 4, 5}}, + {"null", nullptr} + }; + + SECTION("reference access to object_t") + { + using test_type = json::object_t; + json value = {{"one", 1}, {"two", 2}}; + + // check if references are returned correctly + test_type& p1 = value.get_ref(); + CHECK(&p1 == value.get_ptr()); + CHECK(p1 == value.get()); + + const test_type& p2 = value.get_ref(); + CHECK(&p2 == value.get_ptr()); + CHECK(p2 == value.get()); + + // check if mismatching references throw correctly + CHECK_NOTHROW(value.get_ref()); + CHECK_THROWS(value.get_ref()); + CHECK_THROWS(value.get_ref()); + CHECK_THROWS(value.get_ref()); + CHECK_THROWS(value.get_ref()); + CHECK_THROWS(value.get_ref()); + } + + SECTION("const reference access to const object_t") + { + using test_type = json::object_t; + const json value = {{"one", 1}, {"two", 2}}; + + // this should not compile + // test_type& p1 = value.get_ref(); + + // check if references are returned correctly + const test_type& p2 = value.get_ref(); + CHECK(&p2 == value.get_ptr()); + CHECK(p2 == value.get()); + } + + SECTION("reference access to array_t") + { + using test_type = json::array_t; + json value = {1, 2, 3, 4}; + + // check if references are returned correctly + test_type& p1 = value.get_ref(); + CHECK(&p1 == value.get_ptr()); + CHECK(p1 == value.get()); + + const test_type& p2 = value.get_ref(); + CHECK(&p2 == value.get_ptr()); + CHECK(p2 == value.get()); + + // check if mismatching references throw correctly + CHECK_THROWS(value.get_ref()); + CHECK_NOTHROW(value.get_ref()); + CHECK_THROWS(value.get_ref()); + CHECK_THROWS(value.get_ref()); + CHECK_THROWS(value.get_ref()); + CHECK_THROWS(value.get_ref()); + } + + SECTION("reference access to string_t") + { + using test_type = json::string_t; + json value = "hello"; + + // check if references are returned correctly + test_type& p1 = value.get_ref(); + CHECK(&p1 == value.get_ptr()); + CHECK(p1 == value.get()); + + const test_type& p2 = value.get_ref(); + CHECK(&p2 == value.get_ptr()); + CHECK(p2 == value.get()); + + // check if mismatching references throw correctly + CHECK_THROWS(value.get_ref()); + CHECK_THROWS(value.get_ref()); + CHECK_NOTHROW(value.get_ref()); + CHECK_THROWS(value.get_ref()); + CHECK_THROWS(value.get_ref()); + CHECK_THROWS(value.get_ref()); + } + + SECTION("reference access to boolean_t") + { + using test_type = json::boolean_t; + json value = false; + + // check if references are returned correctly + test_type& p1 = value.get_ref(); + CHECK(&p1 == value.get_ptr()); + CHECK(p1 == value.get()); + + const test_type& p2 = value.get_ref(); + CHECK(&p2 == value.get_ptr()); + CHECK(p2 == value.get()); + + // check if mismatching references throw correctly + CHECK_THROWS(value.get_ref()); + CHECK_THROWS(value.get_ref()); + CHECK_THROWS(value.get_ref()); + CHECK_NOTHROW(value.get_ref()); + CHECK_THROWS(value.get_ref()); + CHECK_THROWS(value.get_ref()); + } + + SECTION("reference access to number_integer_t") + { + using test_type = json::number_integer_t; + json value = 23; + + // check if references are returned correctly + test_type& p1 = value.get_ref(); + CHECK(&p1 == value.get_ptr()); + CHECK(p1 == value.get()); + + const test_type& p2 = value.get_ref(); + CHECK(&p2 == value.get_ptr()); + CHECK(p2 == value.get()); + + // check if mismatching references throw correctly + CHECK_THROWS(value.get_ref()); + CHECK_THROWS(value.get_ref()); + CHECK_THROWS(value.get_ref()); + CHECK_THROWS(value.get_ref()); + CHECK_NOTHROW(value.get_ref()); + CHECK_THROWS(value.get_ref()); + } + + SECTION("reference access to number_float_t") + { + using test_type = json::number_float_t; + json value = 42.23; + + // check if references are returned correctly + test_type& p1 = value.get_ref(); + CHECK(&p1 == value.get_ptr()); + CHECK(p1 == value.get()); + + const test_type& p2 = value.get_ref(); + CHECK(&p2 == value.get_ptr()); + CHECK(p2 == value.get()); + + // check if mismatching references throw correctly + CHECK_THROWS(value.get_ref()); + CHECK_THROWS(value.get_ref()); + CHECK_THROWS(value.get_ref()); + CHECK_THROWS(value.get_ref()); + CHECK_THROWS(value.get_ref()); + CHECK_NOTHROW(value.get_ref()); + } +} + TEST_CASE("element access") { SECTION("array")