| #pragma once |
| |
| #include <algorithm> // all_of |
| #include <cctype> // isdigit |
| #include <limits> // max |
| #include <numeric> // accumulate |
| #include <string> // string |
| #include <utility> // move |
| #include <vector> // vector |
| |
| #include <nlohmann/detail/exceptions.hpp> |
| #include <nlohmann/detail/macro_scope.hpp> |
| #include <nlohmann/detail/value_t.hpp> |
| |
| namespace nlohmann |
| { |
| template<typename BasicJsonType> |
| class json_pointer |
| { |
| // allow basic_json to access private members |
| NLOHMANN_BASIC_JSON_TPL_DECLARATION |
| friend class basic_json; |
| |
| public: |
| /*! |
| @brief create JSON pointer |
| |
| Create a JSON pointer according to the syntax described in |
| [Section 3 of RFC6901](https://tools.ietf.org/html/rfc6901#section-3). |
| |
| @param[in] s string representing the JSON pointer; if omitted, the empty |
| string is assumed which references the whole JSON value |
| |
| @throw parse_error.107 if the given JSON pointer @a s is nonempty and does |
| not begin with a slash (`/`); see example below |
| |
| @throw parse_error.108 if a tilde (`~`) in the given JSON pointer @a s is |
| not followed by `0` (representing `~`) or `1` (representing `/`); see |
| example below |
| |
| @liveexample{The example shows the construction several valid JSON pointers |
| as well as the exceptional behavior.,json_pointer} |
| |
| @since version 2.0.0 |
| */ |
| explicit json_pointer(const std::string& s = "") |
| : reference_tokens(split(s)) |
| {} |
| |
| /*! |
| @brief return a string representation of the JSON pointer |
| |
| @invariant For each JSON pointer `ptr`, it holds: |
| @code {.cpp} |
| ptr == json_pointer(ptr.to_string()); |
| @endcode |
| |
| @return a string representation of the JSON pointer |
| |
| @liveexample{The example shows the result of `to_string`.,json_pointer__to_string} |
| |
| @since version 2.0.0 |
| */ |
| std::string to_string() const |
| { |
| return std::accumulate(reference_tokens.begin(), reference_tokens.end(), |
| std::string{}, |
| [](const std::string & a, const std::string & b) |
| { |
| return a + "/" + escape(b); |
| }); |
| } |
| |
| /// @copydoc to_string() |
| operator std::string() const |
| { |
| return to_string(); |
| } |
| |
| /*! |
| @brief append another JSON pointer at the end of this JSON pointer |
| |
| @param[in] ptr JSON pointer to append |
| @return JSON pointer with @a ptr appended |
| |
| @liveexample{The example shows the usage of `operator/=`.,json_pointer__operator_add} |
| |
| @complexity Linear in the length of @a ptr. |
| |
| @sa @ref operator/=(std::string) to append a reference token |
| @sa @ref operator/=(std::size_t) to append an array index |
| @sa @ref operator/(const json_pointer&, const json_pointer&) for a binary operator |
| |
| @since version 3.6.0 |
| */ |
| json_pointer& operator/=(const json_pointer& ptr) |
| { |
| reference_tokens.insert(reference_tokens.end(), |
| ptr.reference_tokens.begin(), |
| ptr.reference_tokens.end()); |
| return *this; |
| } |
| |
| /*! |
| @brief append an unescaped reference token at the end of this JSON pointer |
| |
| @param[in] token reference token to append |
| @return JSON pointer with @a token appended without escaping @a token |
| |
| @liveexample{The example shows the usage of `operator/=`.,json_pointer__operator_add} |
| |
| @complexity Amortized constant. |
| |
| @sa @ref operator/=(const json_pointer&) to append a JSON pointer |
| @sa @ref operator/=(std::size_t) to append an array index |
| @sa @ref operator/(const json_pointer&, std::size_t) for a binary operator |
| |
| @since version 3.6.0 |
| */ |
| json_pointer& operator/=(std::string token) |
| { |
| push_back(std::move(token)); |
| return *this; |
| } |
| |
| /*! |
| @brief append an array index at the end of this JSON pointer |
| |
| @param[in] array_idx array index to append |
| @return JSON pointer with @a array_idx appended |
| |
| @liveexample{The example shows the usage of `operator/=`.,json_pointer__operator_add} |
| |
| @complexity Amortized constant. |
| |
| @sa @ref operator/=(const json_pointer&) to append a JSON pointer |
| @sa @ref operator/=(std::string) to append a reference token |
| @sa @ref operator/(const json_pointer&, std::string) for a binary operator |
| |
| @since version 3.6.0 |
| */ |
| json_pointer& operator/=(std::size_t array_idx) |
| { |
| return *this /= std::to_string(array_idx); |
| } |
| |
| /*! |
| @brief create a new JSON pointer by appending the right JSON pointer at the end of the left JSON pointer |
| |
| @param[in] lhs JSON pointer |
| @param[in] rhs JSON pointer |
| @return a new JSON pointer with @a rhs appended to @a lhs |
| |
| @liveexample{The example shows the usage of `operator/`.,json_pointer__operator_add_binary} |
| |
| @complexity Linear in the length of @a lhs and @a rhs. |
| |
| @sa @ref operator/=(const json_pointer&) to append a JSON pointer |
| |
| @since version 3.6.0 |
| */ |
| friend json_pointer operator/(const json_pointer& lhs, |
| const json_pointer& rhs) |
| { |
| return json_pointer(lhs) /= rhs; |
| } |
| |
| /*! |
| @brief create a new JSON pointer by appending the unescaped token at the end of the JSON pointer |
| |
| @param[in] ptr JSON pointer |
| @param[in] token reference token |
| @return a new JSON pointer with unescaped @a token appended to @a ptr |
| |
| @liveexample{The example shows the usage of `operator/`.,json_pointer__operator_add_binary} |
| |
| @complexity Linear in the length of @a ptr. |
| |
| @sa @ref operator/=(std::string) to append a reference token |
| |
| @since version 3.6.0 |
| */ |
| friend json_pointer operator/(const json_pointer& ptr, std::string token) |
| { |
| return json_pointer(ptr) /= std::move(token); |
| } |
| |
| /*! |
| @brief create a new JSON pointer by appending the array-index-token at the end of the JSON pointer |
| |
| @param[in] ptr JSON pointer |
| @param[in] array_idx array index |
| @return a new JSON pointer with @a array_idx appended to @a ptr |
| |
| @liveexample{The example shows the usage of `operator/`.,json_pointer__operator_add_binary} |
| |
| @complexity Linear in the length of @a ptr. |
| |
| @sa @ref operator/=(std::size_t) to append an array index |
| |
| @since version 3.6.0 |
| */ |
| friend json_pointer operator/(const json_pointer& ptr, std::size_t array_idx) |
| { |
| return json_pointer(ptr) /= array_idx; |
| } |
| |
| /*! |
| @brief returns the parent of this JSON pointer |
| |
| @return parent of this JSON pointer; in case this JSON pointer is the root, |
| the root itself is returned |
| |
| @complexity Linear in the length of the JSON pointer. |
| |
| @liveexample{The example shows the result of `parent_pointer` for different |
| JSON Pointers.,json_pointer__parent_pointer} |
| |
| @since version 3.6.0 |
| */ |
| json_pointer parent_pointer() const |
| { |
| if (empty()) |
| { |
| return *this; |
| } |
| |
| json_pointer res = *this; |
| res.pop_back(); |
| return res; |
| } |
| |
| /*! |
| @brief remove last reference token |
| |
| @pre not `empty()` |
| |
| @liveexample{The example shows the usage of `pop_back`.,json_pointer__pop_back} |
| |
| @complexity Constant. |
| |
| @throw out_of_range.405 if JSON pointer has no parent |
| |
| @since version 3.6.0 |
| */ |
| void pop_back() |
| { |
| if (JSON_HEDLEY_UNLIKELY(empty())) |
| { |
| JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); |
| } |
| |
| reference_tokens.pop_back(); |
| } |
| |
| /*! |
| @brief return last reference token |
| |
| @pre not `empty()` |
| @return last reference token |
| |
| @liveexample{The example shows the usage of `back`.,json_pointer__back} |
| |
| @complexity Constant. |
| |
| @throw out_of_range.405 if JSON pointer has no parent |
| |
| @since version 3.6.0 |
| */ |
| const std::string& back() const |
| { |
| if (JSON_HEDLEY_UNLIKELY(empty())) |
| { |
| JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); |
| } |
| |
| return reference_tokens.back(); |
| } |
| |
| /*! |
| @brief append an unescaped token at the end of the reference pointer |
| |
| @param[in] token token to add |
| |
| @complexity Amortized constant. |
| |
| @liveexample{The example shows the result of `push_back` for different |
| JSON Pointers.,json_pointer__push_back} |
| |
| @since version 3.6.0 |
| */ |
| void push_back(const std::string& token) |
| { |
| reference_tokens.push_back(token); |
| } |
| |
| /// @copydoc push_back(const std::string&) |
| void push_back(std::string&& token) |
| { |
| reference_tokens.push_back(std::move(token)); |
| } |
| |
| /*! |
| @brief return whether pointer points to the root document |
| |
| @return true iff the JSON pointer points to the root document |
| |
| @complexity Constant. |
| |
| @exceptionsafety No-throw guarantee: this function never throws exceptions. |
| |
| @liveexample{The example shows the result of `empty` for different JSON |
| Pointers.,json_pointer__empty} |
| |
| @since version 3.6.0 |
| */ |
| bool empty() const noexcept |
| { |
| return reference_tokens.empty(); |
| } |
| |
| private: |
| /*! |
| @param[in] s reference token to be converted into an array index |
| |
| @return integer representation of @a s |
| |
| @throw parse_error.106 if an array index begins with '0' |
| @throw parse_error.109 if an array index begins not with a digit |
| @throw out_of_range.404 if string @a s could not be converted to an integer |
| @throw out_of_range.410 if an array index exceeds size_type |
| */ |
| static typename BasicJsonType::size_type array_index(const std::string& s) |
| { |
| using size_type = typename BasicJsonType::size_type; |
| |
| // error condition (cf. RFC 6901, Sect. 4) |
| if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && s[0] == '0')) |
| { |
| JSON_THROW(detail::parse_error::create(106, 0, |
| "array index '" + s + |
| "' must not begin with '0'")); |
| } |
| |
| // error condition (cf. RFC 6901, Sect. 4) |
| if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && !(s[0] >= '1' && s[0] <= '9'))) |
| { |
| JSON_THROW(detail::parse_error::create(109, 0, "array index '" + s + "' is not a number")); |
| } |
| |
| std::size_t processed_chars = 0; |
| unsigned long long res = 0; |
| JSON_TRY |
| { |
| res = std::stoull(s, &processed_chars); |
| } |
| JSON_CATCH(std::out_of_range&) |
| { |
| JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + s + "'")); |
| } |
| |
| // check if the string was completely read |
| if (JSON_HEDLEY_UNLIKELY(processed_chars != s.size())) |
| { |
| JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + s + "'")); |
| } |
| |
| // only triggered on special platforms (like 32bit), see also |
| // https://github.com/nlohmann/json/pull/2203 |
| if (res >= static_cast<unsigned long long>((std::numeric_limits<size_type>::max)())) |
| { |
| JSON_THROW(detail::out_of_range::create(410, "array index " + s + " exceeds size_type")); // LCOV_EXCL_LINE |
| } |
| |
| return static_cast<size_type>(res); |
| } |
| |
| JSON_PRIVATE_UNLESS_TESTED: |
| json_pointer top() const |
| { |
| if (JSON_HEDLEY_UNLIKELY(empty())) |
| { |
| JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); |
| } |
| |
| json_pointer result = *this; |
| result.reference_tokens = {reference_tokens[0]}; |
| return result; |
| } |
| |
| private: |
| /*! |
| @brief create and return a reference to the pointed to value |
| |
| @complexity Linear in the number of reference tokens. |
| |
| @throw parse_error.109 if array index is not a number |
| @throw type_error.313 if value cannot be unflattened |
| */ |
| BasicJsonType& get_and_create(BasicJsonType& j) const |
| { |
| auto result = &j; |
| |
| // in case no reference tokens exist, return a reference to the JSON value |
| // j which will be overwritten by a primitive value |
| for (const auto& reference_token : reference_tokens) |
| { |
| 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 |
| result = &result->operator[](reference_token); |
| break; |
| } |
| |
| case detail::value_t::array: |
| { |
| // create an entry in the array |
| result = &result->operator[](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 |
| an error situation, because primitive values may only occur as |
| single value; that is, with an empty list of reference tokens. |
| */ |
| default: |
| JSON_THROW(detail::type_error::create(313, "invalid value to unflatten")); |
| } |
| } |
| |
| return *result; |
| } |
| |
| /*! |
| @brief return a reference to the pointed to value |
| |
| @note This version does not throw if a value is not present, but tries to |
| create nested values instead. For instance, calling this function |
| with pointer `"/this/that"` on a null value is equivalent to calling |
| `operator[]("this").operator[]("that")` on that value, effectively |
| changing the null value to an object. |
| |
| @param[in] ptr a JSON value |
| |
| @return reference to the JSON value pointed to by the JSON pointer |
| |
| @complexity Linear in the length of the JSON pointer. |
| |
| @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 |
| */ |
| BasicJsonType& get_unchecked(BasicJsonType* ptr) const |
| { |
| for (const auto& reference_token : reference_tokens) |
| { |
| // convert null values to arrays or objects before continuing |
| if (ptr->is_null()) |
| { |
| // check if reference token is a number |
| const bool nums = |
| std::all_of(reference_token.begin(), reference_token.end(), |
| [](const unsigned char x) |
| { |
| return std::isdigit(x); |
| }); |
| |
| // change value to array for numbers or "-" or to object otherwise |
| *ptr = (nums || reference_token == "-") |
| ? detail::value_t::array |
| : detail::value_t::object; |
| } |
| |
| switch (ptr->type()) |
| { |
| case detail::value_t::object: |
| { |
| // use unchecked object access |
| ptr = &ptr->operator[](reference_token); |
| break; |
| } |
| |
| case detail::value_t::array: |
| { |
| if (reference_token == "-") |
| { |
| // explicitly treat "-" as index beyond the end |
| ptr = &ptr->operator[](ptr->m_value.array->size()); |
| } |
| else |
| { |
| // convert array index to number; unchecked access |
| ptr = &ptr->operator[](array_index(reference_token)); |
| } |
| break; |
| } |
| |
| default: |
| JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); |
| } |
| } |
| |
| return *ptr; |
| } |
| |
| /*! |
| @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.402 if the array index '-' is used |
| @throw out_of_range.404 if the JSON pointer can not be resolved |
| */ |
| BasicJsonType& get_checked(BasicJsonType* ptr) const |
| { |
| for (const auto& reference_token : reference_tokens) |
| { |
| switch (ptr->type()) |
| { |
| case detail::value_t::object: |
| { |
| // note: at performs range check |
| ptr = &ptr->at(reference_token); |
| break; |
| } |
| |
| case detail::value_t::array: |
| { |
| if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) |
| { |
| // "-" always fails the range check |
| JSON_THROW(detail::out_of_range::create(402, |
| "array index '-' (" + std::to_string(ptr->m_value.array->size()) + |
| ") is out of range")); |
| } |
| |
| // note: at performs range check |
| ptr = &ptr->at(array_index(reference_token)); |
| break; |
| } |
| |
| default: |
| JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); |
| } |
| } |
| |
| return *ptr; |
| } |
| |
| /*! |
| @brief return a const reference to the pointed to value |
| |
| @param[in] ptr a JSON value |
| |
| @return const reference to the JSON value pointed to by the JSON |
| pointer |
| |
| @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.402 if the array index '-' is used |
| @throw out_of_range.404 if the JSON pointer can not be resolved |
| */ |
| const BasicJsonType& get_unchecked(const BasicJsonType* ptr) const |
| { |
| for (const auto& reference_token : reference_tokens) |
| { |
| switch (ptr->type()) |
| { |
| case detail::value_t::object: |
| { |
| // use unchecked object access |
| ptr = &ptr->operator[](reference_token); |
| break; |
| } |
| |
| case detail::value_t::array: |
| { |
| if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) |
| { |
| // "-" cannot be used for const access |
| JSON_THROW(detail::out_of_range::create(402, |
| "array index '-' (" + std::to_string(ptr->m_value.array->size()) + |
| ") is out of range")); |
| } |
| |
| // use unchecked array access |
| ptr = &ptr->operator[](array_index(reference_token)); |
| break; |
| } |
| |
| default: |
| JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); |
| } |
| } |
| |
| return *ptr; |
| } |
| |
| /*! |
| @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.402 if the array index '-' is used |
| @throw out_of_range.404 if the JSON pointer can not be resolved |
| */ |
| const BasicJsonType& get_checked(const BasicJsonType* ptr) const |
| { |
| for (const auto& reference_token : reference_tokens) |
| { |
| switch (ptr->type()) |
| { |
| case detail::value_t::object: |
| { |
| // note: at performs range check |
| ptr = &ptr->at(reference_token); |
| break; |
| } |
| |
| case detail::value_t::array: |
| { |
| if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) |
| { |
| // "-" always fails the range check |
| JSON_THROW(detail::out_of_range::create(402, |
| "array index '-' (" + std::to_string(ptr->m_value.array->size()) + |
| ") is out of range")); |
| } |
| |
| // note: at performs range check |
| ptr = &ptr->at(array_index(reference_token)); |
| break; |
| } |
| |
| default: |
| JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); |
| } |
| } |
| |
| return *ptr; |
| } |
| |
| /*! |
| @throw parse_error.106 if an array index begins with '0' |
| @throw parse_error.109 if an array index was not a number |
| */ |
| bool contains(const BasicJsonType* ptr) const |
| { |
| for (const auto& reference_token : reference_tokens) |
| { |
| switch (ptr->type()) |
| { |
| case detail::value_t::object: |
| { |
| if (!ptr->contains(reference_token)) |
| { |
| // we did not find the key in the object |
| return false; |
| } |
| |
| ptr = &ptr->operator[](reference_token); |
| break; |
| } |
| |
| case detail::value_t::array: |
| { |
| if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) |
| { |
| // "-" always fails the range check |
| return false; |
| } |
| if (JSON_HEDLEY_UNLIKELY(reference_token.size() == 1 && !("0" <= reference_token && reference_token <= "9"))) |
| { |
| // invalid char |
| return false; |
| } |
| if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1)) |
| { |
| if (JSON_HEDLEY_UNLIKELY(!('1' <= reference_token[0] && reference_token[0] <= '9'))) |
| { |
| // first char should be between '1' and '9' |
| return false; |
| } |
| for (std::size_t i = 1; i < reference_token.size(); i++) |
| { |
| if (JSON_HEDLEY_UNLIKELY(!('0' <= reference_token[i] && reference_token[i] <= '9'))) |
| { |
| // other char should be between '0' and '9' |
| return false; |
| } |
| } |
| } |
| |
| const auto idx = array_index(reference_token); |
| if (idx >= ptr->size()) |
| { |
| // index out of range |
| return false; |
| } |
| |
| ptr = &ptr->operator[](idx); |
| break; |
| } |
| |
| default: |
| { |
| // we do not expect primitive values if there is still a |
| // reference token to process |
| return false; |
| } |
| } |
| } |
| |
| // no reference token left means we found a primitive value |
| return true; |
| } |
| |
| /*! |
| @brief split the string input to reference tokens |
| |
| @note This function is only called by the json_pointer constructor. |
| All exceptions below are documented there. |
| |
| @throw parse_error.107 if the pointer is not empty or begins with '/' |
| @throw parse_error.108 if character '~' is not followed by '0' or '1' |
| */ |
| static std::vector<std::string> split(const std::string& reference_string) |
| { |
| std::vector<std::string> result; |
| |
| // special case: empty reference string -> no reference tokens |
| if (reference_string.empty()) |
| { |
| return result; |
| } |
| |
| // check if nonempty reference string begins with slash |
| if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/')) |
| { |
| JSON_THROW(detail::parse_error::create(107, 1, |
| "JSON pointer must be empty or begin with '/' - was: '" + |
| reference_string + "'")); |
| } |
| |
| // extract the reference tokens: |
| // - slash: position of the last read slash (or end of string) |
| // - start: position after the previous slash |
| for ( |
| // search for the first slash after the first character |
| std::size_t slash = reference_string.find_first_of('/', 1), |
| // set the beginning of the first reference token |
| start = 1; |
| // we can stop if start == 0 (if slash == std::string::npos) |
| start != 0; |
| // set the beginning of the next reference token |
| // (will eventually be 0 if slash == std::string::npos) |
| start = (slash == std::string::npos) ? 0 : slash + 1, |
| // find next slash |
| slash = reference_string.find_first_of('/', start)) |
| { |
| // use the text between the beginning of the reference token |
| // (start) and the last slash (slash). |
| auto reference_token = reference_string.substr(start, slash - start); |
| |
| // check reference tokens are properly escaped |
| for (std::size_t pos = reference_token.find_first_of('~'); |
| pos != std::string::npos; |
| pos = reference_token.find_first_of('~', pos + 1)) |
| { |
| JSON_ASSERT(reference_token[pos] == '~'); |
| |
| // ~ must be followed by 0 or 1 |
| if (JSON_HEDLEY_UNLIKELY(pos == reference_token.size() - 1 || |
| (reference_token[pos + 1] != '0' && |
| reference_token[pos + 1] != '1'))) |
| { |
| JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'")); |
| } |
| } |
| |
| // finally, store the reference token |
| unescape(reference_token); |
| result.push_back(reference_token); |
| } |
| |
| return result; |
| } |
| |
| /*! |
| @brief replace all occurrences of a substring by another string |
| |
| @param[in,out] s the string to manipulate; changed so that all |
| occurrences of @a f are replaced with @a t |
| @param[in] f the substring to replace with @a t |
| @param[in] t the string to replace @a f |
| |
| @pre The search string @a f must not be empty. **This precondition is |
| enforced with an assertion.** |
| |
| @since version 2.0.0 |
| */ |
| static void replace_substring(std::string& s, const std::string& f, |
| const std::string& t) |
| { |
| JSON_ASSERT(!f.empty()); |
| for (auto pos = s.find(f); // find first occurrence of f |
| pos != std::string::npos; // make sure f was found |
| s.replace(pos, f.size(), t), // replace with t, and |
| pos = s.find(f, pos + t.size())) // find next occurrence of f |
| {} |
| } |
| |
| JSON_PRIVATE_UNLESS_TESTED: |
| /// escape "~" to "~0" and "/" to "~1" |
| static std::string escape(std::string s) |
| { |
| replace_substring(s, "~", "~0"); |
| replace_substring(s, "/", "~1"); |
| return s; |
| } |
| |
| /// unescape "~1" to tilde and "~0" to slash (order is important!) |
| static void unescape(std::string& s) |
| { |
| replace_substring(s, "~1", "/"); |
| replace_substring(s, "~0", "~"); |
| } |
| |
| private: |
| /*! |
| @param[in] reference_string the reference string to the current value |
| @param[in] value the value to consider |
| @param[in,out] result the result object to insert values to |
| |
| @note Empty objects or arrays are flattened to `null`. |
| */ |
| static void flatten(const std::string& reference_string, |
| const BasicJsonType& value, |
| BasicJsonType& result) |
| { |
| switch (value.type()) |
| { |
| case detail::value_t::array: |
| { |
| if (value.m_value.array->empty()) |
| { |
| // flatten empty array as null |
| result[reference_string] = nullptr; |
| } |
| else |
| { |
| // iterate array and use index as reference string |
| for (std::size_t i = 0; i < value.m_value.array->size(); ++i) |
| { |
| flatten(reference_string + "/" + std::to_string(i), |
| value.m_value.array->operator[](i), result); |
| } |
| } |
| break; |
| } |
| |
| case detail::value_t::object: |
| { |
| if (value.m_value.object->empty()) |
| { |
| // flatten empty object as null |
| result[reference_string] = nullptr; |
| } |
| else |
| { |
| // iterate object and use keys as reference string |
| for (const auto& element : *value.m_value.object) |
| { |
| flatten(reference_string + "/" + escape(element.first), element.second, result); |
| } |
| } |
| break; |
| } |
| |
| default: |
| { |
| // add primitive value with its reference string |
| result[reference_string] = value; |
| break; |
| } |
| } |
| } |
| |
| /*! |
| @param[in] value flattened JSON |
| |
| @return unflattened JSON |
| |
| @throw parse_error.109 if array index is not a number |
| @throw type_error.314 if value is not an object |
| @throw type_error.315 if object values are not primitive |
| @throw type_error.313 if value cannot be unflattened |
| */ |
| static BasicJsonType |
| unflatten(const BasicJsonType& value) |
| { |
| if (JSON_HEDLEY_UNLIKELY(!value.is_object())) |
| { |
| JSON_THROW(detail::type_error::create(314, "only objects can be unflattened")); |
| } |
| |
| BasicJsonType result; |
| |
| // iterate the JSON object values |
| for (const auto& element : *value.m_value.object) |
| { |
| if (JSON_HEDLEY_UNLIKELY(!element.second.is_primitive())) |
| { |
| JSON_THROW(detail::type_error::create(315, "values in object must be primitive")); |
| } |
| |
| // assign value to reference pointed to by JSON pointer; Note that if |
| // the JSON pointer is "" (i.e., points to the whole value), function |
| // get_and_create returns a reference to result itself. An assignment |
| // will then create a primitive value. |
| json_pointer(element.first).get_and_create(result) = element.second; |
| } |
| |
| return result; |
| } |
| |
| /*! |
| @brief compares two JSON pointers for equality |
| |
| @param[in] lhs JSON pointer to compare |
| @param[in] rhs JSON pointer to compare |
| @return whether @a lhs is equal to @a rhs |
| |
| @complexity Linear in the length of the JSON pointer |
| |
| @exceptionsafety No-throw guarantee: this function never throws exceptions. |
| */ |
| friend bool operator==(json_pointer const& lhs, |
| json_pointer const& rhs) noexcept |
| { |
| return lhs.reference_tokens == rhs.reference_tokens; |
| } |
| |
| /*! |
| @brief compares two JSON pointers for inequality |
| |
| @param[in] lhs JSON pointer to compare |
| @param[in] rhs JSON pointer to compare |
| @return whether @a lhs is not equal @a rhs |
| |
| @complexity Linear in the length of the JSON pointer |
| |
| @exceptionsafety No-throw guarantee: this function never throws exceptions. |
| */ |
| friend bool operator!=(json_pointer const& lhs, |
| json_pointer const& rhs) noexcept |
| { |
| return !(lhs == rhs); |
| } |
| |
| /// the reference tokens |
| std::vector<std::string> reference_tokens; |
| }; |
| } // namespace nlohmann |