diff --git a/include/nlohmann/detail/json_pointer.hpp b/include/nlohmann/detail/json_pointer.hpp index 421c5ec8f..d9a3b57ff 100644 --- a/include/nlohmann/detail/json_pointer.hpp +++ b/include/nlohmann/detail/json_pointer.hpp @@ -395,20 +395,6 @@ class json_pointer switch (result->type()) { case detail::value_t::null: - { - if (reference_token == "0") - { - // start a new array if reference token is 0 - result = &result->operator[](0); - } - else - { - // start a new object otherwise - result = &result->operator[](reference_token); - } - break; - } - case detail::value_t::object: { // create an entry in the object @@ -416,13 +402,6 @@ class json_pointer break; } - case detail::value_t::array: - { - // create an entry in the array - result = &result->operator[](static_cast(array_index(reference_token))); - break; - } - /* The following code is only reached if there exists a reference token _and_ the current value is primitive. In this case, we have @@ -437,6 +416,57 @@ class json_pointer return *result; } + /*! + @brief unflatten from object-type JSON to array-type JSON when the keys are continuous numbers + + @param[in] j unflattened JSON with non-array + + @return unflattened JSON + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + static BasicJsonType unflatten_to_array(BasicJsonType& j) + { + // check the keys of object-type value are continuous numbres or not + static auto is_continuous_numbers = [](const BasicJsonType & j) -> bool + { + std::size_t index = 0; + for (auto& item : j.items()) + { + if (std::to_string(index) != item.key()) + { + return false; + } + index++; + } + return true; + }; + + if (j.type() != detail::value_t::object) + { + return j; + } + + using size_type = typename BasicJsonType::size_type; + bool keys_are_continuous_numbers = is_continuous_numbers(j); + BasicJsonType result; + for (auto& item : j.items()) + { + if (keys_are_continuous_numbers) + { + // convert array index to number; unchecked access + result.operator[](static_cast(array_index(item.key()))) = unflatten_to_array(item.value()); + } + else + { + result.operator[](item.key()) = unflatten_to_array(item.value()); + } + } + return result; + } + /*! @brief return a reference to the pointed to value @@ -928,7 +958,7 @@ class json_pointer json_pointer(element.first).get_and_create(result) = element.second; } - return result; + return unflatten_to_array(result); } /*! diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index ed622b9a6..e8b8b07f8 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -11228,20 +11228,6 @@ class json_pointer switch (result->type()) { case detail::value_t::null: - { - if (reference_token == "0") - { - // start a new array if reference token is 0 - result = &result->operator[](0); - } - else - { - // start a new object otherwise - result = &result->operator[](reference_token); - } - break; - } - case detail::value_t::object: { // create an entry in the object @@ -11249,13 +11235,6 @@ class json_pointer break; } - case detail::value_t::array: - { - // create an entry in the array - result = &result->operator[](static_cast(array_index(reference_token))); - break; - } - /* The following code is only reached if there exists a reference token _and_ the current value is primitive. In this case, we have @@ -11270,6 +11249,57 @@ class json_pointer return *result; } + /*! + @brief unflatten from object-type JSON to array-type JSON when the keys are continuous numbers + + @param[in] j unflattened JSON with non-array + + @return unflattened JSON + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + static BasicJsonType unflatten_to_array(BasicJsonType& j) + { + // check the keys of object-type value are continuous numbres or not + static auto is_continuous_numbers = [](const BasicJsonType & j) -> bool + { + std::size_t index = 0; + for (auto& item : j.items()) + { + if (std::to_string(index) != item.key()) + { + return false; + } + index++; + } + return true; + }; + + if (j.type() != detail::value_t::object) + { + return j; + } + + using size_type = typename BasicJsonType::size_type; + bool keys_are_continuous_numbers = is_continuous_numbers(j); + BasicJsonType result; + for (auto& item : j.items()) + { + if (keys_are_continuous_numbers) + { + // convert array index to number; unchecked access + result.operator[](static_cast(array_index(item.key()))) = unflatten_to_array(item.value()); + } + else + { + result.operator[](item.key()) = unflatten_to_array(item.value()); + } + } + return result; + } + /*! @brief return a reference to the pointed to value @@ -11761,7 +11791,7 @@ class json_pointer json_pointer(element.first).get_and_create(result) = element.second; } - return result; + return unflatten_to_array(result); } /*! diff --git a/test/src/unit-json_pointer.cpp b/test/src/unit-json_pointer.cpp index 1e68bc83e..569e7793d 100644 --- a/test/src/unit-json_pointer.cpp +++ b/test/src/unit-json_pointer.cpp @@ -367,10 +367,6 @@ TEST_CASE("JSON pointers") CHECK(not j_const.contains("/one"_json_pointer)); CHECK(not j_const.contains("/one"_json_pointer)); - CHECK_THROWS_AS(json({{"/list/0", 1}, {"/list/1", 2}, {"/list/three", 3}}).unflatten(), json::parse_error&); - CHECK_THROWS_WITH(json({{"/list/0", 1}, {"/list/1", 2}, {"/list/three", 3}}).unflatten(), - "[json.exception.parse_error.109] parse error: array index 'three' is not a number"); - // assign to "-" j["/-"_json_pointer] = 99; CHECK(j == json({1, 13, 3, 33, nullptr, 55, 99})); @@ -509,6 +505,73 @@ TEST_CASE("JSON pointers") CHECK(j_object.flatten().unflatten() == json()); } + SECTION("unflatten") + { + json j = + { + { + "object1", { + {"0", 0}, + {"1", 1}, + {"2", 2}, + } + }, + { + "object2", { + {"0", 0}, + {"1", 1}, + {"two", 2}, + } + }, + { + "object3", { + {"0", 0}, + {"1", 1}, + {"3", 3}, + } + } + }; + + json j_flatten = + { + {"/object1/0", 0}, + {"/object1/1", 1}, + {"/object1/2", 2}, + {"/object2/0", 0}, + {"/object2/1", 1}, + {"/object2/two", 2}, + {"/object3/0", 0}, + {"/object3/1", 1}, + {"/object3/3", 3}, + }; + + json j_unflatten = + { + {"object1", {0, 1, 2}}, + { + "object2", { + {"0", 0}, + {"1", 1}, + {"two", 2}, + } + }, + { + "object3", { + {"0", 0}, + {"1", 1}, + {"3", 3}, + } + } + }; + + // check if flattened result is as expected + CHECK(j.flatten() == j_flatten); + CHECK(j_unflatten.flatten() == j_flatten); + + // check if unflattened result is as expected + CHECK(j_flatten.unflatten() == j_unflatten); + } + SECTION("string representation") { for (auto ptr :