Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
J
json
Project
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Chen Yisong
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
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
1148 additions
and
1072 deletions
+1148
-1072
json.hpp
src/json.hpp
+1148
-1072
No files found.
src/json.hpp
View file @
a3321421
...
@@ -748,8 +748,7 @@ struct is_basic_json_nested_type
...
@@ -748,8 +748,7 @@ struct is_basic_json_nested_type
static
auto
constexpr
value
=
std
::
is_same
<
T
,
typename
BasicJsonType
::
iterator
>::
value
or
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
::
const_iterator
>::
value
or
std
::
is_same
<
T
,
typename
BasicJsonType
::
reverse_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
::
const_reverse_iterator
>::
value
;
std
::
is_same
<
T
,
typename
BasicJsonType
::
json_pointer
>::
value
;
};
};
template
<
class
BasicJsonType
,
class
CompatibleArrayType
>
template
<
class
BasicJsonType
,
class
CompatibleArrayType
>
...
@@ -1320,6 +1319,335 @@ struct adl_serializer
...
@@ -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
@brief a class to store JSON values
...
@@ -1409,13 +1737,14 @@ class basic_json
...
@@ -1409,13 +1737,14 @@ class basic_json
template
<
detail
::
value_t
>
friend
struct
detail
::
external_constructor
;
template
<
detail
::
value_t
>
friend
struct
detail
::
external_constructor
;
/// workaround type for MSVC
/// workaround type for MSVC
using
basic_json_t
=
NLOHMANN_BASIC_JSON_TPL
;
using
basic_json_t
=
NLOHMANN_BASIC_JSON_TPL
;
friend
::
nlohmann
::
json_pointer
;
public
:
public
:
using
value_t
=
detail
::
value_t
;
using
value_t
=
detail
::
value_t
;
// forward declarations
// forward declarations
template
<
typename
U
>
class
iter_impl
;
template
<
typename
U
>
class
iter_impl
;
template
<
typename
Base
>
class
json_reverse_iterator
;
template
<
typename
Base
>
class
json_reverse_iterator
;
class
json_pointer
;
using
json_pointer
=
::
nlohmann
::
json_pointer
;
template
<
typename
T
,
typename
SFINAE
>
template
<
typename
T
,
typename
SFINAE
>
using
json_serializer
=
JSONSerializer
<
T
,
SFINAE
>
;
using
json_serializer
=
JSONSerializer
<
T
,
SFINAE
>
;
...
@@ -13031,617 +13360,367 @@ scan_number_done:
...
@@ -13031,617 +13360,367 @@ scan_number_done:
};
};
public
:
public
:
/*!
//////////////////////////
@brief JSON Pointer
// JSON Pointer support //
//////////////////////////
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
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
(
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
(
out_of_range
::
create
(
405
,
"JSON pointer has no parent"
));
}
json_pointer
result
=
*
this
;
/// @name JSON Pointer functions
result
.
reference_tokens
=
{
reference_tokens
[
0
]};
/// @{
return
result
;
}
/*!
/*!
@brief create and return a reference to the pointed to value
@brief access specified element via JSON Pointer
@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
*/
reference
get_and_create
(
reference
j
)
const
{
pointer
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
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
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
;
}
/*!
Uses a JSON pointer to retrieve a reference to the respective JSON value.
@brief return a reference to the pointed to 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.
@note This version does not throw if a value is not present, but tries
In particular:
to create nested values instead. For instance, calling this function
- If the JSON pointer points to an object key that does not exist, it
with pointer `"/this/that"` on a null value is equivalent to calling
is created an filled with a `null` value before a reference to it
`operator[]("this").operator[]("that")` on that value, effectively
is returned.
changing the null value to an object.
- 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 value
@param[in] ptr a JSON pointer
@return reference to the JSON value pointed to by the JSON pointe
r
@return reference to the element pointed to by @a pt
r
@complexity Linear in the length of the JSON pointer
.
@complexity Constant
.
@throw parse_error.106 if an array index begins with '0'
@throw parse_error.106 if an array index begins with '0'
@throw parse_error.109 if an array index was not a number
@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
@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
@liveexample{The behavior is shown in the example.,operatorjson_pointer}
// otherwise
if
(
nums
or
reference_token
==
"-"
)
{
*
ptr
=
value_t
::
array
;
}
else
{
*
ptr
=
value_t
::
object
;
}
}
switch
(
ptr
->
m_type
)
@since version 2.0.0
{
*/
case
value_t
:
:
object
:
reference
operator
[](
const
json_pointer
&
ptr
)
{
{
// use unchecked object access
return
ptr
.
get_unchecked
(
this
);
ptr
=
&
ptr
->
operator
[](
reference_token
);
break
;
}
}
case
value_t
:
:
array
:
/*!
{
@brief access specified element via JSON Pointer
// 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
==
"-"
)
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
// explicitly treat "-" as index beyond the end
value; no `null` values are created. In particular, the the special value
ptr
=
&
ptr
->
operator
[](
ptr
->
m_value
.
array
->
size
());
`-` yields an exception.
}
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
:
@param[in] ptr JSON pointer to the desired element
{
JSON_THROW
(
out_of_range
::
create
(
404
,
"unresolved reference token '"
+
reference_token
+
"'"
));
}
}
}
return
*
ptr
;
@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.106 if an array index begins with '0'
@throw parse_error.109 if an array index was not a number
@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.402 if the array index '-' is used
@throw out_of_range.404 if the JSON pointer can not be resolved
@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
*/
*/
reference
get_checked
(
pointer
ptr
)
const
const_reference
operator
[](
const
json_pointer
&
ptr
)
const
{
for
(
const
auto
&
reference_token
:
reference_tokens
)
{
switch
(
ptr
->
m_type
)
{
case
value_t
:
:
object
:
{
{
// note: at performs range check
return
ptr
.
get_unchecked
(
this
);
ptr
=
&
ptr
->
at
(
reference_token
);
break
;
}
}
case
value_t
:
:
array
:
/*!
{
@brief access specified element via JSON Pointer
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)
Returns a reference to the element at with specified JSON pointer @a ptr,
if
(
reference_token
.
size
()
>
1
and
reference_token
[
0
]
==
'0'
)
with bounds checking.
{
JSON_THROW
(
parse_error
::
create
(
106
,
0
,
"array index '"
+
reference_token
+
"' must not begin with '0'"
));
}
// note: at performs range check
@param[in] ptr JSON pointer to the desired element
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
:
@return reference to the element pointed to by @a ptr
{
JSON_THROW
(
out_of_range
::
create
(
404
,
"unresolved reference token '"
+
reference_token
+
"'"
));
}
}
}
return
*
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
@brief return a const reference to the pointed to value
is not a number. See example below.
@param[in] ptr a JSON value
@throw out_of_range.401 if an array index in the passed JSON pointer @a ptr
is out of range. See example below.
@return const reference to the JSON value pointed to by the JSON
@throw out_of_range.402 if the array index '-' is used in the passed JSON
pointer
pointer @a ptr. As `at` provides checked access (and no elements are
implicitly inserted), the index '-' is always invalid. See example below.
@throw parse_error.106 if an array index begins with '0'
@throw out_of_range.404 if the JSON pointer @a ptr can not be resolved.
@throw parse_error.109 if an array index was not a number
See example below.
@throw out_of_range.402 if the array index '-' is used
@throw out_of_range.404 if the JSON pointer can not be resolved
@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}
*/
*/
const_reference
get_unchecked
(
const_pointer
ptr
)
const
reference
at
(
const
json_pointer
&
ptr
)
{
for
(
const
auto
&
reference_token
:
reference_tokens
)
{
switch
(
ptr
->
m_type
)
{
case
value_t
:
:
object
:
{
{
// use unchecked object access
return
ptr
.
get_checked
(
this
);
ptr
=
&
ptr
->
operator
[](
reference_token
);
break
;
}
}
case
value_t
:
:
array
:
/*!
{
@brief access specified element via JSON Pointer
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)
Returns a const reference to the element at with specified JSON pointer @a
if
(
reference_token
.
size
()
>
1
and
reference_token
[
0
]
==
'0'
)
ptr, with bounds checking.
{
JSON_THROW
(
parse_error
::
create
(
106
,
0
,
"array index '"
+
reference_token
+
"' must not begin with '0'"
));
}
// use unchecked array access
@param[in] ptr JSON pointer to the desired element
JSON_TRY
@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_const}
*/
const_reference
at
(
const
json_pointer
&
ptr
)
const
{
{
ptr
=
&
ptr
->
operator
[](
static_cast
<
size_type
>
(
std
::
stoi
(
reference_token
))
);
return
ptr
.
get_checked
(
this
);
}
}
JSON_CATCH
(
std
::
invalid_argument
&
)
/*!
@brief return flattened JSON value
The function creates a JSON object whose keys are JSON pointers (see [RFC
6901](https://tools.ietf.org/html/rfc6901)) and whose values are all
primitive. The original JSON value can be restored using the @ref
unflatten() function.
@return an object that maps JSON pointers to primitive values
@note Empty objects and arrays are flattened to `null` and will not be
reconstructed correctly by the @ref unflatten() function.
@complexity Linear in the size the JSON value.
@liveexample{The following code shows how a JSON object is flattened to an
object whose keys consist of JSON pointers.,flatten}
@sa @ref unflatten() for the reverse function
@since version 2.0.0
*/
basic_json
flatten
()
const
{
{
JSON_THROW
(
parse_error
::
create
(
109
,
0
,
"array index '"
+
reference_token
+
"' is not a number"
)
);
basic_json
result
(
value_t
::
object
);
}
json_pointer
::
flatten
(
""
,
*
this
,
result
);
break
;
return
result
;
}
}
default
:
/*!
{
@brief unflatten a previously flattened JSON value
JSON_THROW
(
out_of_range
::
create
(
404
,
"unresolved reference token '"
+
reference_token
+
"'"
));
}
The function restores the arbitrary nesting of a JSON value that has been
}
flattened before using the @ref flatten() function. The JSON value must
}
meet certain constraints:
1. The value must be an object.
2. The keys must be JSON pointers (see
[RFC 6901](https://tools.ietf.org/html/rfc6901))
3. The mapped values must be primitive JSON types.
@return the original JSON from a flattened version
@note Empty objects and arrays are flattened by @ref flatten() to `null`
values and can not unflattened to their original type. Apart from
this example, for a JSON value `j`, the following is always true:
`j == j.flatten().unflatten()`.
return
*
ptr
;
@complexity Linear in the size the JSON value.
}
/*!
@throw type_error.314 if value is not an object
@throw parse_error.106 if an array index begins with '0'
@throw type_error.315 if object values are not primitive
@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
:
@liveexample{The following code shows how a flattened JSON object is
{
unflattened into the original nested JSON object.,unflatten}
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)
@sa @ref flatten() for the reverse function
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
@since version 2.0.0
JSON_TRY
*/
{
basic_json
unflatten
()
const
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"
));
return
json_pointer
::
unflatten
(
*
this
);
}
break
;
}
}
default
:
/// @}
{
JSON_THROW
(
out_of_range
::
create
(
404
,
"unresolved reference token '"
+
reference_token
+
"'"
));
}
}
}
return
*
ptr
;
//////////////////////////
}
// JSON Patch functions //
//////////////////////////
/// @name JSON Patch functions
/// @{
/*!
/*!
@brief split the string input to reference tokens
@brief applies a JSON patch
@note This function is only called by the json_pointer constructor.
[JSON Patch](http://jsonpatch.com) defines a JSON document structure for
All exceptions below are documented there.
expressing a sequence of operations to apply to a JSON) document. With
this function, a JSON Patch is applied to the current JSON value by
executing all operations from the patch.
@throw parse_error.107 if the pointer is not empty or begins with '/'
@param[in] json_patch JSON patch document
@throw parse_error.108 if character '~' is not followed by '0' or '1'
@return patched document
*/
static
std
::
vector
<
std
::
string
>
split
(
const
std
::
string
&
reference_string
)
{
std
::
vector
<
std
::
string
>
result
;
// special case: empty reference string -> no reference tokens
@note The application of a patch is atomic: Either all operations succeed
if
(
reference_string
.
empty
())
and the patched document is returned or an exception is thrown. In
{
any case, the original value is not changed: the patch is applied
return
result
;
to a copy of the value.
}
// check if nonempty reference string begins with slash
@throw parse_error.104 if the JSON patch does not consist of an array of
if
(
reference_string
[
0
]
!=
'/'
)
objects
{
JSON_THROW
(
parse_error
::
create
(
107
,
1
,
"JSON pointer must be empty or begin with '/' - was: '"
+
reference_string
+
"'"
));
}
// extract the reference tokens:
@throw parse_error.105 if the JSON patch is malformed (e.g., mandatory
// - slash: position of the last read slash (or end of string)
attributes are missing); example: `"operation add must have member path"`
// - 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
@throw out_of_range.401 if an array index is out of range.
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
@throw out_of_range.403 if a JSON pointer inside the patch could not be
if
(
pos
==
reference_token
.
size
()
-
1
or
resolved successfully in the current JSON value; example: `"key baz not
(
reference_token
[
pos
+
1
]
!=
'0'
and
found"`
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
@throw out_of_range.405 if JSON pointer has no parent ("add", "remove",
unescape
(
reference_token
);
"move")
result
.
push_back
(
reference_token
);
}
return
result
;
@throw other_error.501 if "test" operation was unsuccessful
}
/*!
@complexity Linear in the size of the JSON value and the length of the
@brief replace all occurrences of a substring by another string
JSON patch. As usually only a fraction of the JSON value is affected by
the patch, the complexity can usually be neglected.
@param[in,out] s the string to manipulate; changed so that all
@liveexample{The following code shows how a JSON patch is applied to a
occurrences of @a f are replaced with @a t
value.,patch}
@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
@sa @ref diff -- create a JSON patch by comparing two JSON values
enforced with an assertion.**
@sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902)
@sa [RFC 6901 (JSON Pointer)](https://tools.ietf.org/html/rfc6901)
@since version 2.0.0
@since version 2.0.0
*/
*/
static
void
replace_substring
(
std
::
string
&
s
,
basic_json
patch
(
const
basic_json
&
json_patch
)
const
const
std
::
string
&
f
,
const
std
::
string
&
t
)
{
{
assert
(
not
f
.
empty
());
// make a working copy to apply the patch to
basic_json
result
=
*
this
;
for
(
// the valid JSON Patch operations
size_t
pos
=
s
.
find
(
f
);
// find first occurrence of f
enum
class
patch_operations
{
add
,
remove
,
replace
,
move
,
copy
,
test
,
invalid
};
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
const
auto
get_op
=
[](
const
std
::
string
&
op
)
static
std
::
string
escape
(
std
::
string
s
)
{
{
// escape "~"" to "~0" and "/" to "~1"
if
(
op
==
"add"
)
replace_substring
(
s
,
"~"
,
"~0"
);
{
replace_substring
(
s
,
"/"
,
"~1"
);
return
patch_operations
::
add
;
return
s
;
}
}
if
(
op
==
"remove"
)
/// unescape tilde and slash
static
void
unescape
(
std
::
string
&
s
)
{
{
// first transform any occurrence of the sequence '~1' to '/'
return
patch_operations
::
remove
;
replace_substring
(
s
,
"~1"
,
"/"
);
// then transform any occurrence of the sequence '~0' to '~'
replace_substring
(
s
,
"~0"
,
"~"
);
}
}
if
(
op
==
"replace"
)
/*!
@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
)
return
patch_operations
::
replace
;
}
if
(
op
==
"move"
)
{
{
case
value_t
:
:
array
:
return
patch_operations
::
move
;
}
if
(
op
==
"copy"
)
{
{
if
(
value
.
m_value
.
array
->
empty
())
return
patch_operations
::
copy
;
}
if
(
op
==
"test"
)
{
{
// flatten empty array as null
return
patch_operations
::
test
;
result
[
reference_string
]
=
nullptr
;
}
}
else
return
patch_operations
::
invalid
;
};
// wrapper for "add" operation; add value at ptr
const
auto
operation_add
=
[
&
result
](
json_pointer
&
ptr
,
basic_json
val
)
{
{
// iterate array and use index as reference string
// adding to the root of the target document means replacing it
for
(
size_t
i
=
0
;
i
<
value
.
m_value
.
array
->
size
();
++
i
)
if
(
ptr
.
is_root
()
)
{
{
flatten
(
reference_string
+
"/"
+
std
::
to_string
(
i
),
result
=
val
;
value
.
m_value
.
array
->
operator
[](
i
),
result
);
}
}
else
{
// make sure the top element of the pointer exists
json_pointer
top_pointer
=
ptr
.
top
();
if
(
top_pointer
!=
ptr
)
{
result
.
at
(
top_pointer
);
}
}
// get reference to parent of JSON pointer ptr
const
auto
last_path
=
ptr
.
pop_back
();
basic_json
&
parent
=
result
[
ptr
];
switch
(
parent
.
m_type
)
{
case
value_t
:
:
null
:
case
value_t
:
:
object
:
{
// use operator[] to add value
parent
[
last_path
]
=
val
;
break
;
break
;
}
}
case
value_t
:
:
object
:
case
value_t
:
:
array
:
{
{
if
(
value
.
m_value
.
object
->
empty
()
)
if
(
last_path
==
"-"
)
{
{
// flatten empty object as null
// special case: append to back
result
[
reference_string
]
=
nullptr
;
parent
.
push_back
(
val
)
;
}
}
else
else
{
{
// iterate object and use keys as reference string
const
auto
idx
=
std
::
stoi
(
last_path
);
for
(
const
auto
&
element
:
*
value
.
m_value
.
object
)
if
(
static_cast
<
size_type
>
(
idx
)
>
parent
.
size
())
{
// avoid undefined behavior
JSON_THROW
(
out_of_range
::
create
(
401
,
"array index "
+
std
::
to_string
(
idx
)
+
" is out of range"
));
}
else
{
{
flatten
(
reference_string
+
"/"
+
escape
(
element
.
first
),
// default case: insert add offset
element
.
second
,
result
);
parent
.
insert
(
parent
.
begin
()
+
static_cast
<
difference_type
>
(
idx
),
val
);
}
}
}
}
break
;
break
;
...
@@ -13649,428 +13728,490 @@ scan_number_done:
...
@@ -13649,428 +13728,490 @@ scan_number_done:
default
:
default
:
{
{
// add primitive value with its reference string
// if there exists a parent it cannot be primitive
result
[
reference_string
]
=
value
;
assert
(
false
);
// LCOV_EXCL_LINE
break
;
}
}
}
}
}
}
};
/*!
// wrapper for "remove" operation; remove value at ptr
@param[in] value flattened JSON
const
auto
operation_remove
=
[
&
result
](
json_pointer
&
ptr
)
{
@return unflattened JSON
// get reference to parent of JSON pointer ptr
const
auto
last_path
=
ptr
.
pop_back
();
basic_json
&
parent
=
result
.
at
(
ptr
);
@throw parse_error.109 if array index is not a number
// remove child
@throw type_error.314 if value is not an object
if
(
parent
.
is_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
())
// perform range check
auto
it
=
parent
.
find
(
last_path
);
if
(
it
!=
parent
.
end
())
{
{
JSON_THROW
(
type_error
::
create
(
314
,
"only objects can be unflattened"
)
);
parent
.
erase
(
it
);
}
}
else
basic_json
result
;
// iterate the JSON object values
for
(
const
auto
&
element
:
*
value
.
m_value
.
object
)
{
{
if
(
not
element
.
second
.
is_primitive
())
JSON_THROW
(
out_of_range
::
create
(
403
,
"key '"
+
last_path
+
"' not found"
));
}
}
else
if
(
parent
.
is_array
())
{
{
JSON_THROW
(
type_error
::
create
(
315
,
"values in object must be primitive"
));
// note erase performs range check
parent
.
erase
(
static_cast
<
size_type
>
(
std
::
stoi
(
last_path
)));
}
}
};
// assign value to reference pointed to by JSON pointer; Note
// type check: top level value must be an array
// that if the JSON pointer is "" (i.e., points to the whole
if
(
not
json_patch
.
is_array
())
// value), function get_and_create returns a reference to
{
// result itself. An assignment will then create a primitive
JSON_THROW
(
parse_error
::
create
(
104
,
0
,
"JSON patch must be an array of objects"
));
// value.
json_pointer
(
element
.
first
).
get_and_create
(
result
)
=
element
.
second
;
}
}
return
result
;
// iterate and apply the operations
}
for
(
const
auto
&
val
:
json_patch
)
{
// wrapper to get a value for an operation
const
auto
get_value
=
[
&
val
](
const
std
::
string
&
op
,
const
std
::
string
&
member
,
bool
string_type
)
->
basic_json
&
{
// find value
auto
it
=
val
.
m_value
.
object
->
find
(
member
);
friend
bool
operator
==
(
json_pointer
const
&
lhs
,
// context-sensitive error message
json_pointer
const
&
rhs
)
noexcept
const
auto
error_msg
=
(
op
==
"op"
)
?
"operation"
:
"operation '"
+
op
+
"'"
;
// check if desired value is present
if
(
it
==
val
.
m_value
.
object
->
end
())
{
{
return
lhs
.
reference_tokens
==
rhs
.
reference_tokens
;
JSON_THROW
(
parse_error
::
create
(
105
,
0
,
error_msg
+
" must have member '"
+
member
+
"'"
))
;
}
}
friend
bool
operator
!=
(
json_pointer
const
&
lhs
,
// check if result is of type string
json_pointer
const
&
rhs
)
noexcept
if
(
string_type
and
not
it
->
second
.
is_string
())
{
{
return
!
(
lhs
==
rhs
);
JSON_THROW
(
parse_error
::
create
(
105
,
0
,
error_msg
+
" must have string member '"
+
member
+
"'"
)
);
}
}
/// the reference tokens
// no error: return value
std
::
vector
<
std
::
string
>
reference_tokens
{}
;
return
it
->
second
;
};
};
//////////////////////////
// type check: every element of the array must be an object
// JSON Pointer support //
if
(
not
val
.
is_object
())
//////////////////////////
/// @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
);
JSON_THROW
(
parse_error
::
create
(
104
,
0
,
"JSON patch must be an array of objects"
)
);
}
}
/*!
// collect mandatory members
@brief access specified element via JSON Pointer
const
std
::
string
op
=
get_value
(
"op"
,
"op"
,
true
);
const
std
::
string
path
=
get_value
(
op
,
"path"
,
true
);
Uses a JSON pointer to retrieve a reference to the respective JSON value.
json_pointer
ptr
(
path
);
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
switch
(
get_op
(
op
))
*/
const_reference
operator
[](
const
json_pointer
&
ptr
)
const
{
{
return
ptr
.
get_unchecked
(
this
);
case
patch_operations
:
:
add
:
{
operation_add
(
ptr
,
get_value
(
"add"
,
"value"
,
false
));
break
;
}
}
/*!
case
patch_operations
:
:
remove
:
@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
);
operation_remove
(
ptr
);
break
;
}
}
/*!
case
patch_operations
:
:
replace
:
@brief access specified element via JSON Pointer
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
@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_const}
*/
const_reference
at
(
const
json_pointer
&
ptr
)
const
{
{
return
ptr
.
get_checked
(
this
);
// the "path" location must exist - use at()
result
.
at
(
ptr
)
=
get_value
(
"replace"
,
"value"
,
false
);
break
;
}
}
/*!
case
patch_operations
:
:
move
:
@brief return flattened JSON value
{
const
std
::
string
from_path
=
get_value
(
"move"
,
"from"
,
true
);
json_pointer
from_ptr
(
from_path
);
The function creates a JSON object whose keys are JSON pointers (see [RFC
// the "from" location must exist - use at()
6901](https://tools.ietf.org/html/rfc6901)) and whose values are all
basic_json
v
=
result
.
at
(
from_ptr
);
primitive. The original JSON value can be restored using the @ref
unflatten() function.
@return an object that maps JSON pointers to primitive values
// The move operation is functionally identical to a
// "remove" operation on the "from" location, followed
// immediately by an "add" operation at the target
// location with the value that was just removed.
operation_remove
(
from_ptr
);
operation_add
(
ptr
,
v
);
break
;
}
@note Empty objects and arrays are flattened to `null` and will not be
case
patch_operations
:
:
copy
:
reconstructed correctly by the @ref unflatten() function.
{
const
std
::
string
from_path
=
get_value
(
"copy"
,
"from"
,
true
);
const
json_pointer
from_ptr
(
from_path
);
@complexity Linear in the size the JSON value.
// the "from" location must exist - use at()
result
[
ptr
]
=
result
.
at
(
from_ptr
);
break
;
}
@liveexample{The following code shows how a JSON object is flattened to an
case
patch_operations
:
:
test
:
object whose keys consist of JSON pointers.,flatten}
{
bool
success
=
false
;
JSON_TRY
{
// check if "value" matches the one at "path"
// the "path" location must exist - use at()
success
=
(
result
.
at
(
ptr
)
==
get_value
(
"test"
,
"value"
,
false
));
}
JSON_CATCH
(
out_of_range
&
)
{
// ignore out of range errors: success remains false
}
@sa @ref unflatten() for the reverse function
// throw an exception if test fails
if
(
not
success
)
{
JSON_THROW
(
other_error
::
create
(
501
,
"unsuccessful: "
+
val
.
dump
()));
}
@since version 2.0.0
break
;
*/
}
basic_json
flatten
()
const
case
patch_operations
:
:
invalid
:
{
{
basic_json
result
(
value_t
::
object
);
// op must be "add", "remove", "replace", "move", "copy", or
json_pointer
::
flatten
(
""
,
*
this
,
result
);
// "test"
JSON_THROW
(
parse_error
::
create
(
105
,
0
,
"operation value '"
+
op
+
"' is invalid"
));
}
}
}
return
result
;
return
result
;
}
}
/*!
/*!
@brief
unflatten a previously flattened JSON value
@brief
creates a diff as a JSON patch
The function restores the arbitrary nesting of a JSON value that has been
Creates a [JSON Patch](http://jsonpatch.com) so that value @a source can
flattened before using the @ref flatten() function. The JSON value must
be changed into the value @a target by calling @ref patch function.
meet certain constraints:
1. The value must be an object.
2. The keys must be JSON pointers (see
[RFC 6901](https://tools.ietf.org/html/rfc6901))
3. The mapped values must be primitive JSON types.
@return the original JSON from a flattened version
@invariant For two JSON values @a source and @a target, the following code
yields always `true`:
@code {.cpp}
source.patch(diff(source, target)) == target;
@endcode
@note Empty objects and arrays are flattened by @ref flatten() to `null`
@note Currently, only `remove`, `add`, and `replace` operations are
values and can not unflattened to their original type. Apart from
generated.
this example, for a JSON value `j`, the following is always true:
`j == j.flatten().unflatten()`.
@complexity Linear in the size the JSON value.
@param[in] source JSON value to compare from
@param[in] target JSON value to compare against
@param[in] path helper value to create JSON pointers
@throw type_error.314 if value is not an object
@return a JSON patch to convert the @a source to @a target
@throw type_error.315 if object values are not primitive
@liveexample{The following code shows how a flattened JSON object is
@complexity Linear in the lengths of @a source and @a target.
unflattened into the original nested JSON object.,unflatten}
@sa @ref flatten() for the reverse function
@liveexample{The following code shows how a JSON patch is created as a
diff for two JSON values.,diff}
@sa @ref patch -- apply a JSON patch
@sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902)
@since version 2.0.0
@since version 2.0.0
*/
*/
basic_json
unflatten
()
const
static
basic_json
diff
(
const
basic_json
&
source
,
const
basic_json
&
target
,
const
std
::
string
&
path
=
""
)
{
{
return
json_pointer
::
unflatten
(
*
this
);
// the patch
}
basic_json
result
(
value_t
::
array
);
/// @}
// if the values are the same, return empty patch
if
(
source
==
target
)
{
return
result
;
}
//////////////////////////
if
(
source
.
type
()
!=
target
.
type
())
// JSON Patch functions //
{
//////////////////////////
// different types: replace value
result
.
push_back
(
{
{
"op"
,
"replace"
},
{
"path"
,
path
},
{
"value"
,
target
}
});
}
else
{
switch
(
source
.
type
())
{
case
value_t
:
:
array
:
{
// first pass: traverse common elements
size_t
i
=
0
;
while
(
i
<
source
.
size
()
and
i
<
target
.
size
())
{
// recursive call to compare array values at index i
auto
temp_diff
=
diff
(
source
[
i
],
target
[
i
],
path
+
"/"
+
std
::
to_string
(
i
));
result
.
insert
(
result
.
end
(),
temp_diff
.
begin
(),
temp_diff
.
end
());
++
i
;
}
/// @name JSON Patch functions
// i now reached the end of at least one array
/// @{
// in a second pass, traverse the remaining elements
/*!
// remove my remaining elements
@brief applies a JSON patch
const
auto
end_index
=
static_cast
<
difference_type
>
(
result
.
size
());
while
(
i
<
source
.
size
())
{
// add operations in reverse order to avoid invalid
// indices
result
.
insert
(
result
.
begin
()
+
end_index
,
object
(
{
{
"op"
,
"remove"
},
{
"path"
,
path
+
"/"
+
std
::
to_string
(
i
)}
}));
++
i
;
}
[JSON Patch](http://jsonpatch.com) defines a JSON document structure for
// add other remaining elements
expressing a sequence of operations to apply to a JSON) document. With
while
(
i
<
target
.
size
())
this function, a JSON Patch is applied to the current JSON value by
{
executing all operations from the patch.
result
.
push_back
(
{
{
"op"
,
"add"
},
{
"path"
,
path
+
"/"
+
std
::
to_string
(
i
)},
{
"value"
,
target
[
i
]}
});
++
i
;
}
@param[in] json_patch JSON patch document
break
;
@return patched document
}
@note The application of a patch is atomic: Either all operations succeed
case
value_t
:
:
object
:
and the patched document is returned or an exception is thrown. In
{
any case, the original value is not changed: the patch is applied
// first pass: traverse this object's elements
to a copy of the value.
for
(
auto
it
=
source
.
begin
();
it
!=
source
.
end
();
++
it
)
{
// escape the key name to be used in a JSON patch
const
auto
key
=
json_pointer
::
escape
(
it
.
key
());
@throw parse_error.104 if the JSON patch does not consist of an array of
if
(
target
.
find
(
it
.
key
())
!=
target
.
end
())
objects
{
// recursive call to compare object values at key it
auto
temp_diff
=
diff
(
it
.
value
(),
target
[
it
.
key
()],
path
+
"/"
+
key
);
result
.
insert
(
result
.
end
(),
temp_diff
.
begin
(),
temp_diff
.
end
());
}
else
{
// found a key that is not in o -> remove it
result
.
push_back
(
object
(
{
{
"op"
,
"remove"
},
{
"path"
,
path
+
"/"
+
key
}
}));
}
}
@throw parse_error.105 if the JSON patch is malformed (e.g., mandatory
// second pass: traverse other object's elements
attributes are missing); example: `"operation add must have member path"`
for
(
auto
it
=
target
.
begin
();
it
!=
target
.
end
();
++
it
)
{
if
(
source
.
find
(
it
.
key
())
==
source
.
end
())
{
// found a key that is not in this -> add it
const
auto
key
=
json_pointer
::
escape
(
it
.
key
());
result
.
push_back
(
{
{
"op"
,
"add"
},
{
"path"
,
path
+
"/"
+
key
},
{
"value"
,
it
.
value
()}
});
}
}
@throw out_of_range.401 if an array index is out of range.
break
;
}
@throw out_of_range.403 if a JSON pointer inside the patch could not be
default
:
resolved successfully in the current JSON value; example: `"key baz not
{
found"`
// both primitive type: replace value
result
.
push_back
(
{
{
"op"
,
"replace"
},
{
"path"
,
path
},
{
"value"
,
target
}
});
break
;
}
}
}
@throw out_of_range.405 if JSON pointer has no parent ("add", "remove",
return
result
;
"move")
}
@throw other_error.501 if "test" operation was unsuccessful
/// @}
};
@complexity Linear in the size of the JSON value and the length of the
/////////////
JSON patch. As usually only a fraction of the JSON value is affected by
// presets //
the patch, the complexity can usually be neglected.
/////////////
@liveexample{The following code shows how a JSON patch is applied to a
/*!
value.,patch}
@brief default JSON class
@sa @ref diff -- create a JSON patch by comparing two JSON values
This type is the default specialization of the @ref basic_json class which
uses the standard template types.
@sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902)
@since version 1.0.0
@sa [RFC 6901 (JSON Pointer)](https://tools.ietf.org/html/rfc6901)
*/
using
json
=
basic_json
<>
;
@since version 2.0.0
//////////////////
*/
// json_pointer //
basic_json
patch
(
const
basic_json
&
json_patch
)
const
//////////////////
{
// make a working copy to apply the patch to
basic_json
result
=
*
this
;
// the valid JSON Patch operations
NLOHMANN_BASIC_JSON_TPL_DECLARATION
enum
class
patch_operations
{
add
,
remove
,
replace
,
move
,
copy
,
test
,
invalid
};
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
;
const
auto
get_op
=
[](
const
std
::
string
&
op
)
// 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
)
{
{
if
(
op
==
"add"
)
switch
(
result
->
m_type
)
{
{
return
patch_operations
::
add
;
case
detail
:
:
value_t
::
null
:
{
if
(
reference_token
==
"0"
)
{
// start a new array if reference token is 0
result
=
&
result
->
operator
[](
0
);
}
}
if
(
op
==
"remove"
)
else
{
{
return
patch_operations
::
remove
;
// start a new object otherwise
result
=
&
result
->
operator
[](
reference_token
);
}
}
if
(
op
==
"replace"
)
break
;
}
case
detail
:
:
value_t
::
object
:
{
{
return
patch_operations
::
replace
;
// create an entry in the object
result
=
&
result
->
operator
[](
reference_token
);
break
;
}
}
if
(
op
==
"move"
)
case
detail
:
:
value_t
::
array
:
{
{
return
patch_operations
::
move
;
// create an entry in the array
JSON_TRY
{
result
=
&
result
->
operator
[](
static_cast
<
size_type
>
(
std
::
stoi
(
reference_token
)));
}
}
if
(
op
==
"copy"
)
JSON_CATCH
(
std
::
invalid_argument
&
)
{
{
return
patch_operations
::
copy
;
JSON_THROW
(
detail
::
parse_error
::
create
(
109
,
0
,
"array index '"
+
reference_token
+
"' is not a number"
));
}
}
if
(
op
==
"test"
)
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
:
{
{
return
patch_operations
::
test
;
JSON_THROW
(
detail
::
type_error
::
create
(
313
,
"invalid value to unflatten"
));
}
}
}
}
return
patch_operations
::
invalid
;
return
*
result
;
};
}
// wrapper for "add" operation; add value at ptr
NLOHMANN_BASIC_JSON_TPL_DECLARATION
const
auto
operation_add
=
[
&
result
](
json_pointer
&
ptr
,
basic_json
val
)
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
)
{
{
// adding to the root of the target document means replacing it
// convert null values to arrays or objects before continuing
if
(
ptr
.
is_root
()
)
if
(
ptr
->
m_type
==
detail
::
value_t
::
null
)
{
{
result
=
val
;
// 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
else
{
{
// make sure the top element of the pointer exists
*
ptr
=
detail
::
value_t
::
object
;
json_pointer
top_pointer
=
ptr
.
top
();
}
if
(
top_pointer
!=
ptr
)
{
result
.
at
(
top_pointer
);
}
}
// get reference to parent of JSON pointer ptr
switch
(
ptr
->
m_type
)
const
auto
last_path
=
ptr
.
pop_back
();
basic_json
&
parent
=
result
[
ptr
];
switch
(
parent
.
m_type
)
{
{
case
value_t
:
:
null
:
case
detail
:
:
value_t
::
object
:
case
value_t
:
:
object
:
{
{
// use operator[] to add value
// use unchecked object access
parent
[
last_path
]
=
val
;
ptr
=
&
ptr
->
operator
[](
reference_token
)
;
break
;
break
;
}
}
case
value_t
:
:
array
:
case
detail
:
:
value_t
::
array
:
{
{
if
(
last_path
==
"-"
)
// error condition (cf. RFC 6901, Sect. 4)
if
(
reference_token
.
size
()
>
1
and
reference_token
[
0
]
==
'0'
)
{
{
// special case: append to back
JSON_THROW
(
detail
::
parse_error
::
create
(
106
,
0
,
parent
.
push_back
(
val
);
"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
else
{
{
const
auto
idx
=
std
::
stoi
(
last_path
);
// convert array index to number; unchecked access
if
(
static_cast
<
size_type
>
(
idx
)
>
parent
.
size
())
JSON_TRY
{
{
// avoid undefined behavior
ptr
=
&
ptr
->
operator
[](
JSON_THROW
(
out_of_range
::
create
(
401
,
"array index "
+
std
::
to_string
(
idx
)
+
" is out of range"
));
static_cast
<
size_type
>
(
std
::
stoi
(
reference_token
)
));
}
}
else
JSON_CATCH
(
std
::
invalid_argument
&
)
{
{
// default case: insert add offset
JSON_THROW
(
detail
::
parse_error
::
create
(
parent
.
insert
(
parent
.
begin
()
+
static_cast
<
difference_type
>
(
idx
),
val
);
109
,
0
,
"array index '"
+
reference_token
+
"' is not a number"
)
);
}
}
}
}
break
;
break
;
...
@@ -14078,351 +14219,286 @@ scan_number_done:
...
@@ -14078,351 +14219,286 @@ scan_number_done:
default
:
default
:
{
{
// if there exists a parent it cannot be primitive
JSON_THROW
(
detail
::
out_of_range
::
create
(
assert
(
false
);
// LCOV_EXCL_LINE
404
,
"unresolved reference token '"
+
reference_token
+
"'"
));
}
}
}
}
}
}
};
// wrapper for "remove" operation; remove value at ptr
return
*
ptr
;
const
auto
operation_remove
=
[
&
result
](
json_pointer
&
ptr
)
}
{
// get reference to parent of JSON pointer ptr
const
auto
last_path
=
ptr
.
pop_back
();
basic_json
&
parent
=
result
.
at
(
ptr
);
// remove child
NLOHMANN_BASIC_JSON_TPL_DECLARATION
if
(
parent
.
is_object
())
NLOHMANN_BASIC_JSON_TPL
&
{
json_pointer
::
get_checked
(
NLOHMANN_BASIC_JSON_TPL
*
ptr
)
const
// perform range check
{
auto
it
=
parent
.
find
(
last_path
);
using
size_type
=
typename
NLOHMANN_BASIC_JSON_TPL
::
size_type
;
if
(
it
!=
parent
.
end
())
for
(
const
auto
&
reference_token
:
reference_tokens
)
{
parent
.
erase
(
it
);
}
else
{
{
JSON_THROW
(
out_of_range
::
create
(
403
,
"key '"
+
last_path
+
"' not found"
));
switch
(
ptr
->
m_type
)
}
}
else
if
(
parent
.
is_array
())
{
{
// note erase performs range check
case
detail
:
:
value_t
::
object
:
parent
.
erase
(
static_cast
<
size_type
>
(
std
::
stoi
(
last_path
)));
}
};
// type check: top level value must be an array
if
(
not
json_patch
.
is_array
())
{
{
JSON_THROW
(
parse_error
::
create
(
104
,
0
,
"JSON patch must be an array of objects"
));
// note: at performs range check
ptr
=
&
ptr
->
at
(
reference_token
);
break
;
}
}
// iterate and apply the operations
case
detail
:
:
value_t
::
array
:
for
(
const
auto
&
val
:
json_patch
)
{
// wrapper to get a value for an operation
const
auto
get_value
=
[
&
val
](
const
std
::
string
&
op
,
const
std
::
string
&
member
,
bool
string_type
)
->
basic_json
&
{
// find value
auto
it
=
val
.
m_value
.
object
->
find
(
member
);
// context-sensitive error message
const
auto
error_msg
=
(
op
==
"op"
)
?
"operation"
:
"operation '"
+
op
+
"'"
;
// check if desired value is present
if
(
it
==
val
.
m_value
.
object
->
end
())
{
{
JSON_THROW
(
parse_error
::
create
(
105
,
0
,
error_msg
+
" must have member '"
+
member
+
"'"
));
if
(
reference_token
==
"-"
)
}
// check if result is of type string
if
(
string_type
and
not
it
->
second
.
is_string
())
{
{
JSON_THROW
(
parse_error
::
create
(
105
,
0
,
error_msg
+
" must have string member '"
+
member
+
"'"
));
// "-" 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"
));
}
}
// no error: return value
// error condition (cf. RFC 6901, Sect. 4)
return
it
->
second
;
if
(
reference_token
.
size
()
>
1
and
reference_token
[
0
]
==
'0'
)
};
// type check: every element of the array must be an object
if
(
not
val
.
is_object
())
{
{
JSON_THROW
(
parse_error
::
create
(
104
,
0
,
"JSON patch must be an array of objects"
));
JSON_THROW
(
detail
::
parse_error
::
create
(
106
,
0
,
"array index '"
+
reference_token
+
"' must not begin with '0'"
));
}
}
// collect mandatory members
// note: at performs range check
const
std
::
string
op
=
get_value
(
"op"
,
"op"
,
true
);
JSON_TRY
const
std
::
string
path
=
get_value
(
op
,
"path"
,
true
);
json_pointer
ptr
(
path
);
switch
(
get_op
(
op
))
{
case
patch_operations
:
:
add
:
{
{
operation_add
(
ptr
,
get_value
(
"add"
,
"value"
,
false
));
ptr
=
&
ptr
->
at
(
static_cast
<
size_type
>
(
std
::
stoi
(
reference_token
)));
break
;
}
}
JSON_CATCH
(
std
::
invalid_argument
&
)
case
patch_operations
:
:
remove
:
{
{
operation_remove
(
ptr
);
JSON_THROW
(
detail
::
parse_error
::
create
(
break
;
109
,
0
,
"array index '"
+
reference_token
+
"' is not a number"
))
;
}
}
case
patch_operations
:
:
replace
:
{
// the "path" location must exist - use at()
result
.
at
(
ptr
)
=
get_value
(
"replace"
,
"value"
,
false
);
break
;
break
;
}
}
case
patch_operations
:
:
move
:
default
:
{
{
const
std
::
string
from_path
=
get_value
(
"move"
,
"from"
,
true
);
JSON_THROW
(
detail
::
out_of_range
::
create
(
json_pointer
from_ptr
(
from_path
);
404
,
"unresolved reference token '"
+
reference_token
+
"'"
));
}
// the "from" location must exist - use at()
}
basic_json
v
=
result
.
at
(
from_ptr
);
// The move operation is functionally identical to a
// "remove" operation on the "from" location, followed
// immediately by an "add" operation at the target
// location with the value that was just removed.
operation_remove
(
from_ptr
);
operation_add
(
ptr
,
v
);
break
;
}
}
case
patch_operations
:
:
copy
:
return
*
ptr
;
{
}
const
std
::
string
from_path
=
get_value
(
"copy"
,
"from"
,
true
);
const
json_pointer
from_ptr
(
from_path
);
// the "from" location must exist - use at()
NLOHMANN_BASIC_JSON_TPL_DECLARATION
result
[
ptr
]
=
result
.
at
(
from_ptr
);
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
;
break
;
}
}
case
patch_operations
:
:
test
:
case
detail
:
:
value_t
::
array
:
{
{
bool
success
=
false
;
if
(
reference_token
==
"-"
)
JSON_TRY
{
{
// check if "value" matches the one at "path"
// "-" cannot be used for const access
// the "path" location must exist - use at()
JSON_THROW
(
detail
::
out_of_range
::
create
(
success
=
(
result
.
at
(
ptr
)
==
get_value
(
"test"
,
"value"
,
false
));
402
,
"array index '-' ("
+
std
::
to_string
(
ptr
->
m_value
.
array
->
size
())
+
") is out of range"
));
}
}
JSON_CATCH
(
out_of_range
&
)
// error condition (cf. RFC 6901, Sect. 4)
if
(
reference_token
.
size
()
>
1
and
reference_token
[
0
]
==
'0'
)
{
{
// ignore out of range errors: success remains false
JSON_THROW
(
detail
::
parse_error
::
create
(
106
,
0
,
"array index '"
+
reference_token
+
"' must not begin with '0'"
));
}
}
// throw an exception if test fail
s
// use unchecked array acces
s
if
(
not
success
)
JSON_TRY
{
{
JSON_THROW
(
other_error
::
create
(
501
,
"unsuccessful: "
+
val
.
dump
()));
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
;
break
;
}
}
case
patch_operations
:
:
invalid
:
default
:
{
{
// op must be "add", "remove", "replace", "move", "copy", or
JSON_THROW
(
detail
::
out_of_range
::
create
(
// "test"
404
,
"unresolved reference token '"
+
reference_token
+
"'"
));
JSON_THROW
(
parse_error
::
create
(
105
,
0
,
"operation value '"
+
op
+
"' is invalid"
));
}
}
}
}
}
}
return
result
;
return
*
ptr
;
}
}
/*!
@brief creates a diff as a JSON patch
Creates a [JSON Patch](http://jsonpatch.com) so that value @a source can
be changed into the value @a target by calling @ref patch function.
@invariant For two JSON values @a source and @a target, the following code
yields always `true`:
@code {.cpp}
source.patch(diff(source, target)) == target;
@endcode
@note Currently, only `remove`, `add`, and `replace` operations are
generated.
@param[in] source JSON value to compare from
@param[in] target JSON value to compare against
@param[in] path helper value to create JSON pointers
@return a JSON patch to convert the @a source to @a target
@complexity Linear in the lengths of @a source and @a target.
@liveexample{The following code shows how a JSON patch is created as a
diff for two JSON values.,diff}
@sa @ref patch -- apply a JSON patch
@sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902)
@since version 2.0.0
NLOHMANN_BASIC_JSON_TPL_DECLARATION
*/
const
NLOHMANN_BASIC_JSON_TPL
&
static
basic_json
diff
(
const
basic_json
&
source
,
json_pointer
::
get_checked
(
const
NLOHMANN_BASIC_JSON_TPL
*
ptr
)
const
const
basic_json
&
target
,
{
const
std
::
string
&
path
=
""
)
using
size_type
=
typename
NLOHMANN_BASIC_JSON_TPL
::
size_type
;
for
(
const
auto
&
reference_token
:
reference_tokens
)
{
{
// the patch
switch
(
ptr
->
m_type
)
basic_json
result
(
value_t
::
array
);
// if the values are the same, return empty patch
if
(
source
==
target
)
{
{
return
result
;
case
detail
:
:
value_t
::
object
:
{
// note: at performs range check
ptr
=
&
ptr
->
at
(
reference_token
);
break
;
}
}
if
(
source
.
type
()
!=
target
.
type
())
case
detail
:
:
value_t
::
array
:
{
{
// different types: replace value
if
(
reference_token
==
"-"
)
result
.
push_back
(
{
{
{
"op"
,
"replace"
},
// "-" always fails the range check
{
"path"
,
path
},
JSON_THROW
(
detail
::
out_of_range
::
create
(
{
"value"
,
target
}
402
,
});
"array index '-' ("
+
std
::
to_string
(
ptr
->
m_value
.
array
->
size
())
+
") is out of range"
));
}
}
else
{
// error condition (cf. RFC 6901, Sect. 4)
switch
(
source
.
type
())
if
(
reference_token
.
size
()
>
1
and
reference_token
[
0
]
==
'0'
)
{
case
value_t
:
:
array
:
{
// first pass: traverse common elements
size_t
i
=
0
;
while
(
i
<
source
.
size
()
and
i
<
target
.
size
())
{
{
// recursive call to compare array values at index i
JSON_THROW
(
detail
::
parse_error
::
create
(
106
,
0
,
auto
temp_diff
=
diff
(
source
[
i
],
target
[
i
],
path
+
"/"
+
std
::
to_string
(
i
));
"array index '"
+
reference_token
+
result
.
insert
(
result
.
end
(),
temp_diff
.
begin
(),
temp_diff
.
end
());
"' must not begin with '0'"
));
++
i
;
}
}
// i now reached the end of at least one array
// note: at performs range check
// in a second pass, traverse the remaining elements
JSON_TRY
// remove my remaining elements
const
auto
end_index
=
static_cast
<
difference_type
>
(
result
.
size
());
while
(
i
<
source
.
size
())
{
{
// add operations in reverse order to avoid invalid
ptr
=
&
ptr
->
at
(
static_cast
<
size_type
>
(
std
::
stoi
(
reference_token
)));
// indices
}
result
.
insert
(
result
.
begin
()
+
end_index
,
object
(
JSON_CATCH
(
std
::
invalid_argument
&
)
{
{
{
"op"
,
"remove"
},
JSON_THROW
(
detail
::
parse_error
::
create
(
{
"path"
,
path
+
"/"
+
std
::
to_string
(
i
)}
109
,
0
,
"array index '"
+
reference_token
+
"' is not a number"
));
}));
}
++
i
;
break
;
}
}
// add other remaining elements
default
:
while
(
i
<
target
.
size
())
{
result
.
push_back
(
{
{
{
"op"
,
"add"
},
JSON_THROW
(
detail
::
out_of_range
::
create
(
{
"path"
,
path
+
"/"
+
std
::
to_string
(
i
)},
404
,
"unresolved reference token '"
+
reference_token
+
"'"
));
{
"value"
,
target
[
i
]}
}
});
++
i
;
}
}
break
;
}
}
case
value_t
:
:
object
:
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
)
{
{
// first pass: traverse this object's elements
case
detail
:
:
value_t
::
array
:
for
(
auto
it
=
source
.
begin
();
it
!=
source
.
end
();
++
it
)
{
{
// escape the key name to be used in a JSON patch
if
(
value
.
m_value
.
array
->
empty
())
const
auto
key
=
json_pointer
::
escape
(
it
.
key
());
if
(
target
.
find
(
it
.
key
())
!=
target
.
end
())
{
{
// recursive call to compare object values at key it
// flatten empty array as null
auto
temp_diff
=
diff
(
it
.
value
(),
target
[
it
.
key
()],
path
+
"/"
+
key
);
result
[
reference_string
]
=
nullptr
;
result
.
insert
(
result
.
end
(),
temp_diff
.
begin
(),
temp_diff
.
end
());
}
}
else
else
{
{
// found a key that is not in o -> remove it
// iterate array and use index as reference string
result
.
push_back
(
object
(
for
(
size_t
i
=
0
;
i
<
value
.
m_value
.
array
->
size
();
++
i
)
{
{
{
"op"
,
"remove"
}
,
flatten
(
reference_string
+
"/"
+
std
::
to_string
(
i
)
,
{
"path"
,
path
+
"/"
+
key
}
value
.
m_value
.
array
->
operator
[](
i
),
result
);
}));
}
}
}
break
;
}
}
// second pass: traverse other object's elements
case
detail
:
:
value_t
::
object
:
for
(
auto
it
=
target
.
begin
();
it
!=
target
.
end
();
++
it
)
{
{
if
(
source
.
find
(
it
.
key
())
==
source
.
end
())
if
(
value
.
m_value
.
object
->
empty
())
{
{
// found a key that is not in this -> add it
// flatten empty object as null
const
auto
key
=
json_pointer
::
escape
(
it
.
key
());
result
[
reference_string
]
=
nullptr
;
result
.
push_back
(
}
else
{
{
{
"op"
,
"add"
},
// iterate object and use keys as reference string
{
"path"
,
path
+
"/"
+
key
},
for
(
const
auto
&
element
:
*
value
.
m_value
.
object
)
{
"value"
,
it
.
value
()}
{
});
flatten
(
reference_string
+
"/"
+
escape
(
element
.
first
),
element
.
second
,
result
);
}
}
}
}
break
;
break
;
}
}
default
:
default
:
{
{
// both primitive type: replace value
// add primitive value with its reference string
result
.
push_back
(
result
[
reference_string
]
=
value
;
{
{
"op"
,
"replace"
},
{
"path"
,
path
},
{
"value"
,
target
}
});
break
;
break
;
}
}
}
}
}
}
return
result
;
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
// presets //
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
@brief default JSON class
// 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
;
}
This type is the default specialization of the @ref basic_json class which
return
result
;
uses the standard template types.
}
@since version 1.0.0
inline
bool
operator
==
(
json_pointer
const
&
lhs
,
json_pointer
const
&
rhs
)
noexcept
*/
{
using
json
=
basic_json
<>
;
return
lhs
.
reference_tokens
==
rhs
.
reference_tokens
;
}
inline
bool
operator
!=
(
json_pointer
const
&
lhs
,
json_pointer
const
&
rhs
)
noexcept
{
return
!
(
lhs
==
rhs
);
}
}
// namespace nlohmann
}
// 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