move json_pointer outside of basic_json

parent fe086d74
......@@ -748,8 +748,7 @@ struct is_basic_json_nested_type
static auto constexpr value = std::is_same<T, typename BasicJsonType::iterator>::value or
std::is_same<T, typename BasicJsonType::const_iterator>::value or
std::is_same<T, typename BasicJsonType::reverse_iterator>::value or
std::is_same<T, typename BasicJsonType::const_reverse_iterator>::value or
std::is_same<T, typename BasicJsonType::json_pointer>::value;
std::is_same<T, typename BasicJsonType::const_reverse_iterator>::value;
};
template<class BasicJsonType, class CompatibleArrayType>
......@@ -1320,6 +1319,335 @@ struct adl_serializer
}
};
/*!
@brief JSON Pointer
A JSON pointer defines a string syntax for identifying a specific value
within a JSON document. It can be used with functions `at` and
`operator[]`. Furthermore, JSON pointers are the base for JSON patches.
@sa [RFC 6901](https://tools.ietf.org/html/rfc6901)
@since version 2.0.0
*/
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 noexcept
{
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();
}
private:
/*!
@brief remove and return last reference pointer
@throw out_of_range.405 if JSON pointer has no parent
*/
std::string pop_back()
{
if (is_root())
{
JSON_THROW(
detail::out_of_range::create(405, "JSON pointer has no parent"));
}
auto last = reference_tokens.back();
reference_tokens.pop_back();
return last;
}
/// return whether pointer points to the root document
bool is_root() const
{
return reference_tokens.empty();
}
json_pointer top() const
{
if (is_root())
{
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;
}
/*!
@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
*/
NLOHMANN_BASIC_JSON_TPL_DECLARATION
NLOHMANN_BASIC_JSON_TPL& get_and_create(NLOHMANN_BASIC_JSON_TPL& j) const;
/*!
@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
*/
NLOHMANN_BASIC_JSON_TPL_DECLARATION
NLOHMANN_BASIC_JSON_TPL& get_unchecked(NLOHMANN_BASIC_JSON_TPL* ptr) const;
/*!
@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
*/
NLOHMANN_BASIC_JSON_TPL_DECLARATION
NLOHMANN_BASIC_JSON_TPL& get_checked(NLOHMANN_BASIC_JSON_TPL* ptr) const;
/*!
@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
*/
NLOHMANN_BASIC_JSON_TPL_DECLARATION
const NLOHMANN_BASIC_JSON_TPL& get_unchecked(const NLOHMANN_BASIC_JSON_TPL* ptr) const;
/*!
@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
*/
NLOHMANN_BASIC_JSON_TPL_DECLARATION
const NLOHMANN_BASIC_JSON_TPL& get_checked(const NLOHMANN_BASIC_JSON_TPL* ptr) const;
/*!
@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 (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
size_t slash = reference_string.find_first_of('/', 1),
// set the beginning of the first reference token
start = 1;
// we can stop if start == string::npos+1 = 0
start != 0;
// set the beginning of the next reference token
// (will eventually be 0 if slash == std::string::npos)
start = 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 (size_t pos = reference_token.find_first_of('~');
pos != std::string::npos;
pos = reference_token.find_first_of('~', pos + 1))
{
assert(reference_token[pos] == '~');
// ~ must be followed by 0 or 1
if (pos == reference_token.size() - 1 or
(reference_token[pos + 1] != '0' and
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)
{
assert(not f.empty());
for (size_t 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
pos = s.find(f, pos + t.size()) // find next occurrence of f
)
;
}
/// escape tilde and slash
static std::string escape(std::string s)
{
// escape "~"" to "~0" and "/" to "~1"
replace_substring(s, "~", "~0");
replace_substring(s, "/", "~1");
return s;
}
/// unescape tilde and slash
static void unescape(std::string& s)
{
// first transform any occurrence of the sequence '~1' to '/'
replace_substring(s, "~1", "/");
// then transform any occurrence of the sequence '~0' to '~'
replace_substring(s, "~0", "~");
}
/*!
@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`.
*/
NLOHMANN_BASIC_JSON_TPL_DECLARATION
static void flatten(const std::string& reference_string,
const NLOHMANN_BASIC_JSON_TPL& value,
NLOHMANN_BASIC_JSON_TPL& result);
/*!
@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
*/
NLOHMANN_BASIC_JSON_TPL_DECLARATION
static NLOHMANN_BASIC_JSON_TPL
unflatten(const NLOHMANN_BASIC_JSON_TPL& value);
friend bool operator==(json_pointer const& lhs,
json_pointer const& rhs) noexcept;
friend bool operator!=(json_pointer const& lhs,
json_pointer const& rhs) noexcept;
/// the reference tokens
std::vector<std::string> reference_tokens;
};
/*!
@brief a class to store JSON values
......@@ -1409,13 +1737,14 @@ class basic_json
template<detail::value_t> friend struct detail::external_constructor;
/// workaround type for MSVC
using basic_json_t = NLOHMANN_BASIC_JSON_TPL;
friend ::nlohmann::json_pointer;
public:
using value_t = detail::value_t;
// forward declarations
template<typename U> class iter_impl;
template<typename Base> class json_reverse_iterator;
class json_pointer;
using json_pointer = ::nlohmann::json_pointer;
template<typename T, typename SFINAE>
using json_serializer = JSONSerializer<T, SFINAE>;
......@@ -13031,803 +13360,124 @@ scan_number_done:
};
public:
//////////////////////////
// JSON Pointer support //
//////////////////////////
/// @name JSON Pointer functions
/// @{
/*!
@brief JSON Pointer
@brief access specified element via JSON Pointer
Uses a JSON pointer to retrieve a reference to the respective JSON value.
No bound checking is performed. Similar to @ref operator[](const typename
object_t::key_type&), `null` values are created in arrays and objects if
necessary.
In particular:
- If the JSON pointer points to an object key that does not exist, it
is created an filled with a `null` value before a reference to it
is returned.
- If the JSON pointer points to an array index that does not exist, it
is created an filled with a `null` value before a reference to it
is returned. All indices between the current maximum and the given
index are also filled with `null`.
- The special value `-` is treated as a synonym for the index past the
end.
@param[in] ptr a JSON pointer
@return reference to the element pointed to by @a ptr
@complexity Constant.
A JSON pointer defines a string syntax for identifying a specific value
within a JSON document. It can be used with functions `at` and
`operator[]`. Furthermore, JSON pointers are the base for JSON patches.
@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
@sa [RFC 6901](https://tools.ietf.org/html/rfc6901)
@liveexample{The behavior is shown in the example.,operatorjson_pointer}
@since version 2.0.0
*/
class json_pointer
reference operator[](const json_pointer& ptr)
{
/// allow basic_json to access private members
friend class basic_json;
return ptr.get_unchecked(this);
}
public:
/*!
@brief create JSON pointer
/*!
@brief access specified element via JSON Pointer
Create a JSON pointer according to the syntax described in
[Section 3 of RFC6901](https://tools.ietf.org/html/rfc6901#section-3).
Uses a JSON pointer to retrieve a reference to the respective JSON value.
No bound checking is performed. The function does not change the JSON
value; no `null` values are created. In particular, the the special value
`-` yields an exception.
@param[in] s string representing the JSON pointer; if omitted, the
empty string is assumed which references the whole JSON
value
@param[in] ptr JSON pointer to the desired element
@throw parse_error.107 if the given JSON pointer @a s is nonempty and
does not begin with a slash (`/`); see example below
@return const reference to the element pointed to by @a ptr
@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
@complexity Constant.
@liveexample{The example shows the construction several valid JSON
pointers as well as the exceptional behavior.,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
@since version 2.0.0
*/
explicit json_pointer(const std::string& s = "")
: reference_tokens(split(s))
{}
@liveexample{The behavior is shown in the example.,operatorjson_pointer_const}
/*!
@brief return a string representation of the JSON pointer
@since version 2.0.0
*/
const_reference operator[](const json_pointer& ptr) const
{
return ptr.get_unchecked(this);
}
@invariant For each JSON pointer `ptr`, it holds:
@code {.cpp}
ptr == json_pointer(ptr.to_string());
@endcode
/*!
@brief access specified element via JSON Pointer
@return a string representation of the JSON pointer
Returns a reference to the element at with specified JSON pointer @a ptr,
with bounds checking.
@liveexample{The example shows the result of `to_string`.,
json_pointer__to_string}
@param[in] ptr JSON pointer to the desired element
@since version 2.0.0
*/
std::string to_string() const noexcept
{
return std::accumulate(reference_tokens.begin(),
reference_tokens.end(), std::string{},
[](const std::string & a, const std::string & b)
{
return a + "/" + escape(b);
});
}
@return reference to the element pointed to by @a ptr
/// @copydoc to_string()
operator std::string() const
{
return to_string();
}
@throw parse_error.106 if an array index in the passed JSON pointer @a ptr
begins with '0'. See example below.
private:
/*!
@brief remove and return last reference pointer
@throw out_of_range.405 if JSON pointer has no parent
*/
std::string pop_back()
{
if (is_root())
{
JSON_THROW(out_of_range::create(405, "JSON pointer has no parent"));
}
@throw parse_error.109 if an array index in the passed JSON pointer @a ptr
is not a number. See example below.
auto last = reference_tokens.back();
reference_tokens.pop_back();
return last;
}
@throw out_of_range.401 if an array index in the passed JSON pointer @a ptr
is out of range. See example below.
/// return whether pointer points to the root document
bool is_root() const
{
return reference_tokens.empty();
}
@throw out_of_range.402 if the array index '-' is used in the passed JSON
pointer @a ptr. As `at` provides checked access (and no elements are
implicitly inserted), the index '-' is always invalid. See example below.
json_pointer top() const
{
if (is_root())
{
JSON_THROW(out_of_range::create(405, "JSON pointer has no parent"));
}
@throw out_of_range.404 if the JSON pointer @a ptr can not be resolved.
See example below.
json_pointer result = *this;
result.reference_tokens = {reference_tokens[0]};
return result;
}
@exceptionsafety Strong guarantee: if an exception is thrown, there are no
changes in the JSON value.
/*!
@brief create and return a reference to the pointed to value
@complexity Constant.
@complexity Linear in the number of reference tokens.
@since version 2.0.0
@throw parse_error.109 if array index is not a number
@throw type_error.313 if value cannot be unflattened
*/
reference get_and_create(reference j) const
{
pointer result = &j;
@liveexample{The behavior is shown in the example.,at_json_pointer}
*/
reference at(const json_pointer& ptr)
{
return ptr.get_checked(this);
}
// 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->m_type)
{
case 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;
}
/*!
@brief access specified element via JSON Pointer
case value_t::object:
{
// create an entry in the object
result = &result->operator[](reference_token);
break;
}
case value_t::array:
{
// create an entry in the array
JSON_TRY
{
result = &result->operator[](static_cast<size_type>(std::stoi(reference_token)));
}
JSON_CATCH (std::invalid_argument&)
{
JSON_THROW(parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
}
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(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
*/
reference get_unchecked(pointer ptr) const
{
for (const auto& reference_token : reference_tokens)
{
// convert null values to arrays or objects before continuing
if (ptr->m_type == value_t::null)
{
// check if reference token is a number
const bool nums = std::all_of(reference_token.begin(),
reference_token.end(),
[](const char x)
{
return (x >= '0' and x <= '9');
});
// change value to array for numbers or "-" or to object
// otherwise
if (nums or reference_token == "-")
{
*ptr = value_t::array;
}
else
{
*ptr = value_t::object;
}
}
switch (ptr->m_type)
{
case value_t::object:
{
// use unchecked object access
ptr = &ptr->operator[](reference_token);
break;
}
case value_t::array:
{
// error condition (cf. RFC 6901, Sect. 4)
if (reference_token.size() > 1 and reference_token[0] == '0')
{
JSON_THROW(parse_error::create(106, 0, "array index '" + reference_token + "' must not begin with '0'"));
}
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
JSON_TRY
{
ptr = &ptr->operator[](static_cast<size_type>(std::stoi(reference_token)));
}
JSON_CATCH (std::invalid_argument&)
{
JSON_THROW(parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
}
}
break;
}
default:
{
JSON_THROW(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
*/
reference get_checked(pointer ptr) const
{
for (const auto& reference_token : reference_tokens)
{
switch (ptr->m_type)
{
case value_t::object:
{
// note: at performs range check
ptr = &ptr->at(reference_token);
break;
}
case value_t::array:
{
if (reference_token == "-")
{
// "-" always fails the range check
JSON_THROW(out_of_range::create(402, "array index '-' (" +
std::to_string(ptr->m_value.array->size()) +
") is out of range"));
}
// error condition (cf. RFC 6901, Sect. 4)
if (reference_token.size() > 1 and reference_token[0] == '0')
{
JSON_THROW(parse_error::create(106, 0, "array index '" + reference_token + "' must not begin with '0'"));
}
// note: at performs range check
JSON_TRY
{
ptr = &ptr->at(static_cast<size_type>(std::stoi(reference_token)));
}
JSON_CATCH (std::invalid_argument&)
{
JSON_THROW(parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
}
break;
}
default:
{
JSON_THROW(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_reference get_unchecked(const_pointer ptr) const
{
for (const auto& reference_token : reference_tokens)
{
switch (ptr->m_type)
{
case value_t::object:
{
// use unchecked object access
ptr = &ptr->operator[](reference_token);
break;
}
case value_t::array:
{
if (reference_token == "-")
{
// "-" cannot be used for const access
JSON_THROW(out_of_range::create(402, "array index '-' (" +
std::to_string(ptr->m_value.array->size()) +
") is out of range"));
}
// error condition (cf. RFC 6901, Sect. 4)
if (reference_token.size() > 1 and reference_token[0] == '0')
{
JSON_THROW(parse_error::create(106, 0, "array index '" + reference_token + "' must not begin with '0'"));
}
// use unchecked array access
JSON_TRY
{
ptr = &ptr->operator[](static_cast<size_type>(std::stoi(reference_token)));
}
JSON_CATCH (std::invalid_argument&)
{
JSON_THROW(parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
}
break;
}
default:
{
JSON_THROW(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_reference get_checked(const_pointer ptr) const
{
for (const auto& reference_token : reference_tokens)
{
switch (ptr->m_type)
{
case value_t::object:
{
// note: at performs range check
ptr = &ptr->at(reference_token);
break;
}
case value_t::array:
{
if (reference_token == "-")
{
// "-" always fails the range check
JSON_THROW(out_of_range::create(402, "array index '-' (" +
std::to_string(ptr->m_value.array->size()) +
") is out of range"));
}
// error condition (cf. RFC 6901, Sect. 4)
if (reference_token.size() > 1 and reference_token[0] == '0')
{
JSON_THROW(parse_error::create(106, 0, "array index '" + reference_token + "' must not begin with '0'"));
}
// note: at performs range check
JSON_TRY
{
ptr = &ptr->at(static_cast<size_type>(std::stoi(reference_token)));
}
JSON_CATCH (std::invalid_argument&)
{
JSON_THROW(parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
}
break;
}
default:
{
JSON_THROW(out_of_range::create(404, "unresolved reference token '" + reference_token + "'"));
}
}
}
return *ptr;
}
/*!
@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 (reference_string[0] != '/')
{
JSON_THROW(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
size_t slash = reference_string.find_first_of('/', 1),
// set the beginning of the first reference token
start = 1;
// we can stop if start == string::npos+1 = 0
start != 0;
// set the beginning of the next reference token
// (will eventually be 0 if slash == std::string::npos)
start = 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 (size_t pos = reference_token.find_first_of('~');
pos != std::string::npos;
pos = reference_token.find_first_of('~', pos + 1))
{
assert(reference_token[pos] == '~');
// ~ must be followed by 0 or 1
if (pos == reference_token.size() - 1 or
(reference_token[pos + 1] != '0' and
reference_token[pos + 1] != '1'))
{
JSON_THROW(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)
{
assert(not f.empty());
for (
size_t 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
pos = s.find(f, pos + t.size()) // find next occurrence of f
);
}
/// escape tilde and slash
static std::string escape(std::string s)
{
// escape "~"" to "~0" and "/" to "~1"
replace_substring(s, "~", "~0");
replace_substring(s, "/", "~1");
return s;
}
/// unescape tilde and slash
static void unescape(std::string& s)
{
// first transform any occurrence of the sequence '~1' to '/'
replace_substring(s, "~1", "/");
// then transform any occurrence of the sequence '~0' to '~'
replace_substring(s, "~0", "~");
}
/*!
@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 basic_json& value,
basic_json& result)
{
switch (value.m_type)
{
case 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 (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 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 basic_json unflatten(const basic_json& value)
{
if (not value.is_object())
{
JSON_THROW(type_error::create(314, "only objects can be unflattened"));
}
basic_json result;
// iterate the JSON object values
for (const auto& element : *value.m_value.object)
{
if (not element.second.is_primitive())
{
JSON_THROW(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;
}
friend bool operator==(json_pointer const& lhs,
json_pointer const& rhs) noexcept
{
return lhs.reference_tokens == rhs.reference_tokens;
}
friend bool operator!=(json_pointer const& lhs,
json_pointer const& rhs) noexcept
{
return !(lhs == rhs);
}
/// the reference tokens
std::vector<std::string> reference_tokens {};
};
//////////////////////////
// JSON Pointer support //
//////////////////////////
/// @name JSON Pointer functions
/// @{
/*!
@brief access specified element via JSON Pointer
Uses a JSON pointer to retrieve a reference to the respective JSON value.
No bound checking is performed. Similar to @ref operator[](const typename
object_t::key_type&), `null` values are created in arrays and objects if
necessary.
In particular:
- If the JSON pointer points to an object key that does not exist, it
is created an filled with a `null` value before a reference to it
is returned.
- If the JSON pointer points to an array index that does not exist, it
is created an filled with a `null` value before a reference to it
is returned. All indices between the current maximum and the given
index are also filled with `null`.
- The special value `-` is treated as a synonym for the index past the
end.
@param[in] ptr a JSON pointer
@return reference to the element pointed to by @a ptr
@complexity Constant.
@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
@liveexample{The behavior is shown in the example.,operatorjson_pointer}
@since version 2.0.0
*/
reference operator[](const json_pointer& ptr)
{
return ptr.get_unchecked(this);
}
/*!
@brief access specified element via JSON Pointer
Uses a JSON pointer to retrieve a reference to the respective JSON value.
No bound checking is performed. The function does not change the JSON
value; no `null` values are created. In particular, the the special value
`-` yields an exception.
@param[in] ptr JSON pointer to the desired element
@return const reference to the element pointed to by @a ptr
@complexity Constant.
@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
@liveexample{The behavior is shown in the example.,operatorjson_pointer_const}
@since version 2.0.0
*/
const_reference operator[](const json_pointer& ptr) const
{
return ptr.get_unchecked(this);
}
/*!
@brief access specified element via JSON Pointer
Returns a reference to the element at with specified JSON pointer @a ptr,
with bounds checking.
@param[in] ptr JSON pointer to the desired element
@return reference to the element pointed to by @a ptr
@throw parse_error.106 if an array index in the passed JSON pointer @a ptr
begins with '0'. See example below.
@throw parse_error.109 if an array index in the passed JSON pointer @a ptr
is not a number. See example below.
@throw out_of_range.401 if an array index in the passed JSON pointer @a ptr
is out of range. See example below.
@throw out_of_range.402 if the array index '-' is used in the passed JSON
pointer @a ptr. As `at` provides checked access (and no elements are
implicitly inserted), the index '-' is always invalid. See example below.
@throw out_of_range.404 if the JSON pointer @a ptr can not be resolved.
See example below.
@exceptionsafety Strong guarantee: if an exception is thrown, there are no
changes in the JSON value.
@complexity Constant.
@since version 2.0.0
@liveexample{The behavior is shown in the example.,at_json_pointer}
*/
reference at(const json_pointer& ptr)
{
return ptr.get_checked(this);
}
/*!
@brief access specified element via JSON Pointer
Returns a const reference to the element at with specified JSON pointer @a
ptr, with bounds checking.
Returns a const reference to the element at with specified JSON pointer @a
ptr, with bounds checking.
@param[in] ptr JSON pointer to the desired element
......@@ -14423,6 +14073,432 @@ uses the standard template types.
@since version 1.0.0
*/
using json = basic_json<>;
//////////////////
// json_pointer //
//////////////////
NLOHMANN_BASIC_JSON_TPL_DECLARATION
NLOHMANN_BASIC_JSON_TPL&
json_pointer::get_and_create(NLOHMANN_BASIC_JSON_TPL& j) const
{
using size_type = typename NLOHMANN_BASIC_JSON_TPL::size_type;
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->m_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
JSON_TRY
{
result = &result->operator[](
static_cast<size_type>(std::stoi(reference_token)));
}
JSON_CATCH(std::invalid_argument&)
{
JSON_THROW(detail::parse_error::create(
109, 0, "array index '" + reference_token + "' is not a number"));
}
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;
}
NLOHMANN_BASIC_JSON_TPL_DECLARATION
NLOHMANN_BASIC_JSON_TPL&
json_pointer::get_unchecked(NLOHMANN_BASIC_JSON_TPL* ptr) const
{
using size_type = typename NLOHMANN_BASIC_JSON_TPL::size_type;
for (const auto& reference_token : reference_tokens)
{
// convert null values to arrays or objects before continuing
if (ptr->m_type == detail::value_t::null)
{
// check if reference token is a number
const bool nums =
std::all_of(reference_token.begin(), reference_token.end(),
[](const char x)
{
return (x >= '0' and x <= '9');
});
// change value to array for numbers or "-" or to object
// otherwise
if (nums or reference_token == "-")
{
*ptr = detail::value_t::array;
}
else
{
*ptr = detail::value_t::object;
}
}
switch (ptr->m_type)
{
case detail::value_t::object:
{
// use unchecked object access
ptr = &ptr->operator[](reference_token);
break;
}
case detail::value_t::array:
{
// error condition (cf. RFC 6901, Sect. 4)
if (reference_token.size() > 1 and reference_token[0] == '0')
{
JSON_THROW(detail::parse_error::create(106, 0,
"array index '" + reference_token +
"' must not begin with '0'"));
}
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
JSON_TRY
{
ptr = &ptr->operator[](
static_cast<size_type>(std::stoi(reference_token)));
}
JSON_CATCH(std::invalid_argument&)
{
JSON_THROW(detail::parse_error::create(
109, 0, "array index '" + reference_token + "' is not a number"));
}
}
break;
}
default:
{
JSON_THROW(detail::out_of_range::create(
404, "unresolved reference token '" + reference_token + "'"));
}
}
}
return *ptr;
}
NLOHMANN_BASIC_JSON_TPL_DECLARATION
NLOHMANN_BASIC_JSON_TPL&
json_pointer::get_checked(NLOHMANN_BASIC_JSON_TPL* ptr) const
{
using size_type = typename NLOHMANN_BASIC_JSON_TPL::size_type;
for (const auto& reference_token : reference_tokens)
{
switch (ptr->m_type)
{
case detail::value_t::object:
{
// note: at performs range check
ptr = &ptr->at(reference_token);
break;
}
case detail::value_t::array:
{
if (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"));
}
// error condition (cf. RFC 6901, Sect. 4)
if (reference_token.size() > 1 and reference_token[0] == '0')
{
JSON_THROW(detail::parse_error::create(106, 0,
"array index '" + reference_token +
"' must not begin with '0'"));
}
// note: at performs range check
JSON_TRY
{
ptr = &ptr->at(static_cast<size_type>(std::stoi(reference_token)));
}
JSON_CATCH(std::invalid_argument&)
{
JSON_THROW(detail::parse_error::create(
109, 0, "array index '" + reference_token + "' is not a number"));
}
break;
}
default:
{
JSON_THROW(detail::out_of_range::create(
404, "unresolved reference token '" + reference_token + "'"));
}
}
}
return *ptr;
}
NLOHMANN_BASIC_JSON_TPL_DECLARATION
const NLOHMANN_BASIC_JSON_TPL&
json_pointer::get_unchecked(const NLOHMANN_BASIC_JSON_TPL* ptr) const
{
using size_type = typename NLOHMANN_BASIC_JSON_TPL::size_type;
for (const auto& reference_token : reference_tokens)
{
switch (ptr->m_type)
{
case detail::value_t::object:
{
// use unchecked object access
ptr = &ptr->operator[](reference_token);
break;
}
case detail::value_t::array:
{
if (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"));
}
// error condition (cf. RFC 6901, Sect. 4)
if (reference_token.size() > 1 and reference_token[0] == '0')
{
JSON_THROW(detail::parse_error::create(106, 0,
"array index '" + reference_token +
"' must not begin with '0'"));
}
// use unchecked array access
JSON_TRY
{
ptr = &ptr->operator[](
static_cast<size_type>(std::stoi(reference_token)));
}
JSON_CATCH(std::invalid_argument&)
{
JSON_THROW(detail::parse_error::create(
109, 0, "array index '" + reference_token + "' is not a number"));
}
break;
}
default:
{
JSON_THROW(detail::out_of_range::create(
404, "unresolved reference token '" + reference_token + "'"));
}
}
}
return *ptr;
}
NLOHMANN_BASIC_JSON_TPL_DECLARATION
const NLOHMANN_BASIC_JSON_TPL&
json_pointer::get_checked(const NLOHMANN_BASIC_JSON_TPL* ptr) const
{
using size_type = typename NLOHMANN_BASIC_JSON_TPL::size_type;
for (const auto& reference_token : reference_tokens)
{
switch (ptr->m_type)
{
case detail::value_t::object:
{
// note: at performs range check
ptr = &ptr->at(reference_token);
break;
}
case detail::value_t::array:
{
if (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"));
}
// error condition (cf. RFC 6901, Sect. 4)
if (reference_token.size() > 1 and reference_token[0] == '0')
{
JSON_THROW(detail::parse_error::create(106, 0,
"array index '" + reference_token +
"' must not begin with '0'"));
}
// note: at performs range check
JSON_TRY
{
ptr = &ptr->at(static_cast<size_type>(std::stoi(reference_token)));
}
JSON_CATCH(std::invalid_argument&)
{
JSON_THROW(detail::parse_error::create(
109, 0, "array index '" + reference_token + "' is not a number"));
}
break;
}
default:
{
JSON_THROW(detail::out_of_range::create(
404, "unresolved reference token '" + reference_token + "'"));
}
}
}
return *ptr;
}
NLOHMANN_BASIC_JSON_TPL_DECLARATION
void json_pointer::flatten(const std::string& reference_string,
const NLOHMANN_BASIC_JSON_TPL& value,
NLOHMANN_BASIC_JSON_TPL& result)
{
switch (value.m_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 (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;
}
}
}
NLOHMANN_BASIC_JSON_TPL_DECLARATION
NLOHMANN_BASIC_JSON_TPL
json_pointer::unflatten(const NLOHMANN_BASIC_JSON_TPL& value)
{
if (not value.is_object())
{
JSON_THROW(detail::type_error::create(314, "only objects can be unflattened"));
}
NLOHMANN_BASIC_JSON_TPL result;
// iterate the JSON object values
for (const auto& element : *value.m_value.object)
{
if (not 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;
}
inline bool operator==(json_pointer const& lhs, json_pointer const& rhs) noexcept
{
return lhs.reference_tokens == rhs.reference_tokens;
}
inline bool operator!=(json_pointer const& lhs, json_pointer const& rhs) noexcept
{
return !(lhs == rhs);
}
} // namespace nlohmann
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment