Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
J
json
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Libraries
json
Commits
a3321421
Unverified
Commit
a3321421
authored
Jul 01, 2017
by
Théo DELRIEU
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
move json_pointer outside of basic_json
parent
fe086d74
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
846 additions
and
770 deletions
+846
-770
src/json.hpp
src/json.hpp
+846
-770
No files found.
src/json.hpp
View file @
a3321421
...
...
@@ -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
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment