From cd04a7d3e952385617d43fd412d90fece7b586e9 Mon Sep 17 00:00:00 2001 From: Niels Date: Tue, 15 Dec 2015 08:38:54 +0100 Subject: [PATCH] fix for #133 added value() function to get object value at given key or a default value if key does not exist --- README.md | 2 +- doc/examples/basic_json__value.cpp | 29 ++++++++ doc/examples/basic_json__value.link | 1 + doc/examples/basic_json__value.output | 1 + src/json.hpp | 102 +++++++++++++++++++++++++- src/json.hpp.re2c | 102 +++++++++++++++++++++++++- test/unit.cpp | 95 ++++++++++++++++++++++++ 7 files changed, 325 insertions(+), 7 deletions(-) create mode 100644 doc/examples/basic_json__value.cpp create mode 100644 doc/examples/basic_json__value.link create mode 100644 doc/examples/basic_json__value.output diff --git a/README.md b/README.md index a3f7c869..a77961f2 100644 --- a/README.md +++ b/README.md @@ -395,7 +395,7 @@ $ make $ ./json_unit "*" =============================================================================== -All tests passed (3341803 assertions in 28 test cases) +All tests passed (3341846 assertions in 28 test cases) ``` For more information, have a look at the file [.travis.yml](https://github.com/nlohmann/json/blob/master/.travis.yml). diff --git a/doc/examples/basic_json__value.cpp b/doc/examples/basic_json__value.cpp new file mode 100644 index 00000000..37081791 --- /dev/null +++ b/doc/examples/basic_json__value.cpp @@ -0,0 +1,29 @@ +#include + +using namespace nlohmann; + +int main() +{ + // create a JSON object with different entry types + json j = + { + {"integer", 1}, + {"floating", 42.23}, + {"string", "hello world"}, + {"boolean", true}, + {"object", {{"key1", 1}, {"key2", 2}}}, + {"array", {1, 2, 3}} + }; + + // access existing values + int v_integer = j.value("integer", 0); + double v_floating = j.value("floating", 47.11); + + // access nonexisting values and rely on default value + std::string v_string = j.value("nonexisting", "oops"); + bool v_boolean = j.value("nonexisting", false); + + // output values + std::cout << std::boolalpha << v_integer << " " << v_floating + << " " << v_string << " " << v_boolean << "\n"; +} diff --git a/doc/examples/basic_json__value.link b/doc/examples/basic_json__value.link new file mode 100644 index 00000000..6698c891 --- /dev/null +++ b/doc/examples/basic_json__value.link @@ -0,0 +1 @@ +online \ No newline at end of file diff --git a/doc/examples/basic_json__value.output b/doc/examples/basic_json__value.output new file mode 100644 index 00000000..dfc40e58 --- /dev/null +++ b/doc/examples/basic_json__value.output @@ -0,0 +1 @@ +1 42.23 oops false diff --git a/src/json.hpp b/src/json.hpp index 75ab3f5b..782657e1 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -2514,6 +2514,10 @@ class basic_json @liveexample{The example below shows how object elements can be read and written using at.,at__object_t_key_type} + + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + @sa @ref value() for access by value with a default value */ reference at(const typename object_t::key_type& key) { @@ -2546,6 +2550,10 @@ class basic_json @liveexample{The example below shows how object elements can be read using at.,at__object_t_key_type_const} + + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + @sa @ref value() for access by value with a default value */ const_reference at(const typename object_t::key_type& key) const { @@ -2655,6 +2663,10 @@ class basic_json @liveexample{The example below shows how object elements can be read and written using the [] operator.,operatorarray__key_type} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value */ reference operator[](const typename object_t::key_type& key) { @@ -2697,6 +2709,10 @@ class basic_json @liveexample{The example below shows how object elements can be read and written using the [] operator.,operatorarray__key_type} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value */ template reference operator[](const T (&key)[n]) @@ -2719,6 +2735,86 @@ class basic_json } } + /*! + @brief access specified object element with default value + + Returns either a copy of an object's element at the specified key @a key or + a given default value if no element with key @a key exists. + + The function is basically equivalent to executing + @code{.cpp} + try { + return at(key); + } catch(std::out_of_range) { + return default_value; + } + @endcode + + @note Unlike @ref at(const typename object_t::key_type&), this function + does not throw if the given key @a key was not found. + + @note Unlike @ref operator[](const typename object_t::key_type& key), this + function does not implicitly add an element to the position defined by @a + key. This function is furthermore also applicable to const objects. + + @param[in] key key of the element to access + @param[in] default_value the value to return if @a key is not found + + @tparam ValueType type compatible to JSON values, for instance `int` for + JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for + JSON arrays. Note the type of the expected value at @a key and the default + value @a default_value must be compatible. + + @return copy of the element at key @a key or @a default_value if @a key + is not found + + @throw std::domain_error if JSON is not an object + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be queried + with a default value.,basic_json__value} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + */ + template ::value + , int>::type = 0> + ValueType value(const typename object_t::key_type& key, ValueType default_value) const + { + // at only works for objects + if (is_object()) + { + // if key is found, return value and given default value otherwise + const auto it = find(key); + if (it != end()) + { + return *it; + } + else + { + return default_value; + } + } + else + { + throw std::domain_error("cannot use value() with " + type_name()); + } + } + + /*! + // overload for a default value of type const char* + @copydoc basic_json::value() + */ + string_t value(const typename object_t::key_type& key, const char* default_value) const + { + return value(key, string_t(default_value)); + } + /*! @brief access the first element @@ -5660,9 +5756,9 @@ class basic_json /*! @brief wrapper to access iterator member functions in range-based for - This class allows to access @ref key() and @ref value() during range-based - for loops. In these loops, a reference to the JSON values is returned, so - there is no access to the underlying iterator. + This class allows to access @ref iterator::key() and @ref iterator::value() + during range-based for loops. In these loops, a reference to the JSON + values is returned, so there is no access to the underlying iterator. */ class iterator_wrapper { diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c index 6c419a89..682abb1c 100644 --- a/src/json.hpp.re2c +++ b/src/json.hpp.re2c @@ -2514,6 +2514,10 @@ class basic_json @liveexample{The example below shows how object elements can be read and written using at.,at__object_t_key_type} + + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + @sa @ref value() for access by value with a default value */ reference at(const typename object_t::key_type& key) { @@ -2546,6 +2550,10 @@ class basic_json @liveexample{The example below shows how object elements can be read using at.,at__object_t_key_type_const} + + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + @sa @ref value() for access by value with a default value */ const_reference at(const typename object_t::key_type& key) const { @@ -2655,6 +2663,10 @@ class basic_json @liveexample{The example below shows how object elements can be read and written using the [] operator.,operatorarray__key_type} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value */ reference operator[](const typename object_t::key_type& key) { @@ -2697,6 +2709,10 @@ class basic_json @liveexample{The example below shows how object elements can be read and written using the [] operator.,operatorarray__key_type} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value */ template reference operator[](const T (&key)[n]) @@ -2719,6 +2735,86 @@ class basic_json } } + /*! + @brief access specified object element with default value + + Returns either a copy of an object's element at the specified key @a key or + a given default value if no element with key @a key exists. + + The function is basically equivalent to executing + @code{.cpp} + try { + return at(key); + } catch(std::out_of_range) { + return default_value; + } + @endcode + + @note Unlike @ref at(const typename object_t::key_type&), this function + does not throw if the given key @a key was not found. + + @note Unlike @ref operator[](const typename object_t::key_type& key), this + function does not implicitly add an element to the position defined by @a + key. This function is furthermore also applicable to const objects. + + @param[in] key key of the element to access + @param[in] default_value the value to return if @a key is not found + + @tparam ValueType type compatible to JSON values, for instance `int` for + JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for + JSON arrays. Note the type of the expected value at @a key and the default + value @a default_value must be compatible. + + @return copy of the element at key @a key or @a default_value if @a key + is not found + + @throw std::domain_error if JSON is not an object + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be queried + with a default value.,basic_json__value} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + */ + template ::value + , int>::type = 0> + ValueType value(const typename object_t::key_type& key, ValueType default_value) const + { + // at only works for objects + if (is_object()) + { + // if key is found, return value and given default value otherwise + const auto it = find(key); + if (it != end()) + { + return *it; + } + else + { + return default_value; + } + } + else + { + throw std::domain_error("cannot use value() with " + type_name()); + } + } + + /*! + // overload for a default value of type const char* + @copydoc basic_json::value() + */ + string_t value(const typename object_t::key_type& key, const char* default_value) const + { + return value(key, string_t(default_value)); + } + /*! @brief access the first element @@ -5660,9 +5756,9 @@ class basic_json /*! @brief wrapper to access iterator member functions in range-based for - This class allows to access @ref key() and @ref value() during range-based - for loops. In these loops, a reference to the JSON values is returned, so - there is no access to the underlying iterator. + This class allows to access @ref iterator::key() and @ref iterator::value() + during range-based for loops. In these loops, a reference to the JSON + values is returned, so there is no access to the underlying iterator. */ class iterator_wrapper { diff --git a/test/unit.cpp b/test/unit.cpp index 37baf50c..8826d835 100644 --- a/test/unit.cpp +++ b/test/unit.cpp @@ -3082,6 +3082,101 @@ TEST_CASE("element access") } } + SECTION("access specified element with default value") + { + SECTION("access existing value") + { + CHECK(j.value("integer", 2) == 1); + CHECK(j.value("integer", 1.0) == Approx(1)); + CHECK(j.value("null", json(1)) == json()); + CHECK(j.value("boolean", false) == true); + CHECK(j.value("string", "bar") == "hello world"); + CHECK(j.value("string", std::string("bar")) == "hello world"); + CHECK(j.value("floating", 12.34) == Approx(42.23)); + CHECK(j.value("floating", 12) == 42); + CHECK(j.value("object", json({{"foo", "bar"}})) == json(json::object())); + CHECK(j.value("array", json({10, 100})) == json({1, 2, 3})); + + CHECK(j_const.value("integer", 2) == 1); + CHECK(j_const.value("integer", 1.0) == Approx(1)); + CHECK(j_const.value("boolean", false) == true); + CHECK(j_const.value("string", "bar") == "hello world"); + CHECK(j_const.value("string", std::string("bar")) == "hello world"); + CHECK(j_const.value("floating", 12.34) == Approx(42.23)); + CHECK(j_const.value("floating", 12) == 42); + CHECK(j_const.value("object", json({{"foo", "bar"}})) == json(json::object())); + CHECK(j_const.value("array", json({10, 100})) == json({1, 2, 3})); + } + + SECTION("access non-existing value") + { + CHECK(j.value("_", 2) == 2); + CHECK(j.value("_", false) == false); + CHECK(j.value("_", "bar") == "bar"); + CHECK(j.value("_", 12.34) == Approx(12.34)); + CHECK(j.value("_", json({{"foo", "bar"}})) == json({{"foo", "bar"}})); + CHECK(j.value("_", json({10, 100})) == json({10, 100})); + + CHECK(j_const.value("_", 2) == 2); + CHECK(j_const.value("_", false) == false); + CHECK(j_const.value("_", "bar") == "bar"); + CHECK(j_const.value("_", 12.34) == Approx(12.34)); + CHECK(j_const.value("_", json({{"foo", "bar"}})) == json({{"foo", "bar"}})); + CHECK(j_const.value("_", json({10, 100})) == json({10, 100})); + } + + SECTION("access on non-object type") + { + SECTION("null") + { + json j_nonobject(json::value_t::null); + const json j_nonobject_const(j_nonobject); + CHECK_THROWS_AS(j_nonobject.value("foo", 1), std::domain_error); + CHECK_THROWS_AS(j_nonobject_const.value("foo", 1), std::domain_error); + } + + SECTION("boolean") + { + json j_nonobject(json::value_t::boolean); + const json j_nonobject_const(j_nonobject); + CHECK_THROWS_AS(j_nonobject.value("foo", 1), std::domain_error); + CHECK_THROWS_AS(j_nonobject_const.value("foo", 1), std::domain_error); + } + + SECTION("string") + { + json j_nonobject(json::value_t::string); + const json j_nonobject_const(j_nonobject); + CHECK_THROWS_AS(j_nonobject.value("foo", 1), std::domain_error); + CHECK_THROWS_AS(j_nonobject_const.value("foo", 1), std::domain_error); + } + + SECTION("array") + { + json j_nonobject(json::value_t::array); + const json j_nonobject_const(j_nonobject); + CHECK_THROWS_AS(j_nonobject.value("foo", 1), std::domain_error); + CHECK_THROWS_AS(j_nonobject_const.value("foo", 1), std::domain_error); + } + + SECTION("number (integer)") + { + json j_nonobject(json::value_t::number_integer); + const json j_nonobject_const(j_nonobject); + CHECK_THROWS_AS(j_nonobject.value("foo", 1), std::domain_error); + CHECK_THROWS_AS(j_nonobject_const.value("foo", 1), std::domain_error); + } + + SECTION("number (floating-point)") + { + json j_nonobject(json::value_t::number_float); + const json j_nonobject_const(j_nonobject); + CHECK_THROWS_AS(j_nonobject.value("foo", 1), std::domain_error); + CHECK_THROWS_AS(j_nonobject_const.value("foo", 1), std::domain_error); + } + } + } + SECTION("front and back") { // "array" is the smallest key