mirror of https://github.com/PurpleI2P/i2pd.git
I2P: End-to-End encrypted and anonymous Internet
https://i2pd.website/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
826 lines
30 KiB
826 lines
30 KiB
#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/string_escape.hpp> |
|
#include <nlohmann/detail/value_t.hpp> |
|
|
|
namespace nlohmann |
|
{ |
|
|
|
/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document |
|
/// @sa https://json.nlohmann.me/api/json_pointer/ |
|
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 |
|
/// @sa https://json.nlohmann.me/api/json_pointer/json_pointer/ |
|
explicit json_pointer(const std::string& s = "") |
|
: reference_tokens(split(s)) |
|
{} |
|
|
|
/// @brief return a string representation of the JSON pointer |
|
/// @sa https://json.nlohmann.me/api/json_pointer/to_string/ |
|
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 + "/" + detail::escape(b); |
|
}); |
|
} |
|
|
|
/// @brief return a string representation of the JSON pointer |
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_string/ |
|
operator std::string() const |
|
{ |
|
return to_string(); |
|
} |
|
|
|
/// @brief append another JSON pointer at the end of this JSON pointer |
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/ |
|
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 |
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/ |
|
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 |
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/ |
|
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 |
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/ |
|
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 |
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/ |
|
friend json_pointer operator/(const json_pointer& lhs, std::string token) // NOLINT(performance-unnecessary-value-param) |
|
{ |
|
return json_pointer(lhs) /= std::move(token); |
|
} |
|
|
|
/// @brief create a new JSON pointer by appending the array-index-token at the end of the JSON pointer |
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/ |
|
friend json_pointer operator/(const json_pointer& lhs, std::size_t array_idx) |
|
{ |
|
return json_pointer(lhs) /= array_idx; |
|
} |
|
|
|
/// @brief returns the parent of this JSON pointer |
|
/// @sa https://json.nlohmann.me/api/json_pointer/parent_pointer/ |
|
json_pointer parent_pointer() const |
|
{ |
|
if (empty()) |
|
{ |
|
return *this; |
|
} |
|
|
|
json_pointer res = *this; |
|
res.pop_back(); |
|
return res; |
|
} |
|
|
|
/// @brief remove last reference token |
|
/// @sa https://json.nlohmann.me/api/json_pointer/pop_back/ |
|
void pop_back() |
|
{ |
|
if (JSON_HEDLEY_UNLIKELY(empty())) |
|
{ |
|
JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", BasicJsonType())); |
|
} |
|
|
|
reference_tokens.pop_back(); |
|
} |
|
|
|
/// @brief return last reference token |
|
/// @sa https://json.nlohmann.me/api/json_pointer/back/ |
|
const std::string& back() const |
|
{ |
|
if (JSON_HEDLEY_UNLIKELY(empty())) |
|
{ |
|
JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", BasicJsonType())); |
|
} |
|
|
|
return reference_tokens.back(); |
|
} |
|
|
|
/// @brief append an unescaped token at the end of the reference pointer |
|
/// @sa https://json.nlohmann.me/api/json_pointer/push_back/ |
|
void push_back(const std::string& token) |
|
{ |
|
reference_tokens.push_back(token); |
|
} |
|
|
|
/// @brief append an unescaped token at the end of the reference pointer |
|
/// @sa https://json.nlohmann.me/api/json_pointer/push_back/ |
|
void push_back(std::string&& token) |
|
{ |
|
reference_tokens.push_back(std::move(token)); |
|
} |
|
|
|
/// @brief return whether pointer points to the root document |
|
/// @sa https://json.nlohmann.me/api/json_pointer/empty/ |
|
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'", BasicJsonType())); |
|
} |
|
|
|
// 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", BasicJsonType())); |
|
} |
|
|
|
std::size_t processed_chars = 0; |
|
unsigned long long res = 0; // NOLINT(runtime/int) |
|
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 + "'", BasicJsonType())); |
|
} |
|
|
|
// 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 + "'", BasicJsonType())); |
|
} |
|
|
|
// 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)())) // NOLINT(runtime/int) |
|
{ |
|
JSON_THROW(detail::out_of_range::create(410, "array index " + s + " exceeds size_type", BasicJsonType())); // 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", BasicJsonType())); |
|
} |
|
|
|
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. |
|
*/ |
|
case detail::value_t::string: |
|
case detail::value_t::boolean: |
|
case detail::value_t::number_integer: |
|
case detail::value_t::number_unsigned: |
|
case detail::value_t::number_float: |
|
case detail::value_t::binary: |
|
case detail::value_t::discarded: |
|
default: |
|
JSON_THROW(detail::type_error::create(313, "invalid value to unflatten", j)); |
|
} |
|
} |
|
|
|
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; |
|
} |
|
|
|
case detail::value_t::null: |
|
case detail::value_t::string: |
|
case detail::value_t::boolean: |
|
case detail::value_t::number_integer: |
|
case detail::value_t::number_unsigned: |
|
case detail::value_t::number_float: |
|
case detail::value_t::binary: |
|
case detail::value_t::discarded: |
|
default: |
|
JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'", *ptr)); |
|
} |
|
} |
|
|
|
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", *ptr)); |
|
} |
|
|
|
// note: at performs range check |
|
ptr = &ptr->at(array_index(reference_token)); |
|
break; |
|
} |
|
|
|
case detail::value_t::null: |
|
case detail::value_t::string: |
|
case detail::value_t::boolean: |
|
case detail::value_t::number_integer: |
|
case detail::value_t::number_unsigned: |
|
case detail::value_t::number_float: |
|
case detail::value_t::binary: |
|
case detail::value_t::discarded: |
|
default: |
|
JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'", *ptr)); |
|
} |
|
} |
|
|
|
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", *ptr)); |
|
} |
|
|
|
// use unchecked array access |
|
ptr = &ptr->operator[](array_index(reference_token)); |
|
break; |
|
} |
|
|
|
case detail::value_t::null: |
|
case detail::value_t::string: |
|
case detail::value_t::boolean: |
|
case detail::value_t::number_integer: |
|
case detail::value_t::number_unsigned: |
|
case detail::value_t::number_float: |
|
case detail::value_t::binary: |
|
case detail::value_t::discarded: |
|
default: |
|
JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'", *ptr)); |
|
} |
|
} |
|
|
|
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", *ptr)); |
|
} |
|
|
|
// note: at performs range check |
|
ptr = &ptr->at(array_index(reference_token)); |
|
break; |
|
} |
|
|
|
case detail::value_t::null: |
|
case detail::value_t::string: |
|
case detail::value_t::boolean: |
|
case detail::value_t::number_integer: |
|
case detail::value_t::number_unsigned: |
|
case detail::value_t::number_float: |
|
case detail::value_t::binary: |
|
case detail::value_t::discarded: |
|
default: |
|
JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'", *ptr)); |
|
} |
|
} |
|
|
|
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; |
|
} |
|
|
|
case detail::value_t::null: |
|
case detail::value_t::string: |
|
case detail::value_t::boolean: |
|
case detail::value_t::number_integer: |
|
case detail::value_t::number_unsigned: |
|
case detail::value_t::number_float: |
|
case detail::value_t::binary: |
|
case detail::value_t::discarded: |
|
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 + "'", BasicJsonType())); |
|
} |
|
|
|
// 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'", BasicJsonType())); |
|
} |
|
} |
|
|
|
// finally, store the reference token |
|
detail::unescape(reference_token); |
|
result.push_back(reference_token); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
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 + "/" + detail::escape(element.first), element.second, result); |
|
} |
|
} |
|
break; |
|
} |
|
|
|
case detail::value_t::null: |
|
case detail::value_t::string: |
|
case detail::value_t::boolean: |
|
case detail::value_t::number_integer: |
|
case detail::value_t::number_unsigned: |
|
case detail::value_t::number_float: |
|
case detail::value_t::binary: |
|
case detail::value_t::discarded: |
|
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", value)); |
|
} |
|
|
|
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", element.second)); |
|
} |
|
|
|
// 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
|
|
|