Merge branch 'feature/json-patch' into develop
This commit is contained in:
commit
688cd49bdc
21 changed files with 2785 additions and 311 deletions
|
@ -109,6 +109,8 @@ matrix:
|
|||
env: COMPILER=clang
|
||||
|
||||
script:
|
||||
- uname -a
|
||||
- $COMPILER --version
|
||||
- make CXX=$COMPILER CXXFLAGS="-lstdc++"
|
||||
- ./json_unit "*"
|
||||
- if [ `which valgrind` ]; then
|
||||
|
|
|
@ -5,10 +5,12 @@ All notable changes to this project will be documented in this file. This projec
|
|||
|
||||
[Full Changelog](https://github.com/nlohmann/json/compare/v1.1.0...HEAD)
|
||||
|
||||
- Additional integration options [\#237](https://github.com/nlohmann/json/issues/237)
|
||||
- Can't use basic\_json::iterator as a base iterator for std::move\_iterator [\#233](https://github.com/nlohmann/json/issues/233)
|
||||
- Provide a FAQ [\#163](https://github.com/nlohmann/json/issues/163)
|
||||
- Create PULL\_REQUEST\_TEMPLATE.md [\#213](https://github.com/nlohmann/json/pull/213) ([whackashoe](https://github.com/whackashoe))
|
||||
- fixed noexcept; added constexpr [\#208](https://github.com/nlohmann/json/pull/208) ([nlohmann](https://github.com/nlohmann))
|
||||
- Add support for afl-fuzz testing [\#207](https://github.com/nlohmann/json/pull/207) ([msm-](https://github.com/msm-))
|
||||
- Add support for afl-fuzz testing [\#207](https://github.com/nlohmann/json/pull/207) ([mykter](https://github.com/mykter))
|
||||
- Issue \#178 - Extending support to full uint64\_t/int64\_t range and unsigned type \(updated\) [\#193](https://github.com/nlohmann/json/pull/193) ([twelsby](https://github.com/twelsby))
|
||||
|
||||
- double values are serialized with commas as decimal points [\#228](https://github.com/nlohmann/json/issues/228)
|
||||
|
@ -26,6 +28,7 @@ All notable changes to this project will be documented in this file. This projec
|
|||
- Conflicting typedef of ssize\_t on Windows 32 bit when using Boost.Python [\#204](https://github.com/nlohmann/json/issues/204)
|
||||
- Integer conversion to unsigned [\#178](https://github.com/nlohmann/json/issues/178)
|
||||
|
||||
- Implement additional integration options [\#238](https://github.com/nlohmann/json/pull/238) ([robertmrk](https://github.com/robertmrk))
|
||||
- make serialization locale-independent [\#232](https://github.com/nlohmann/json/pull/232) ([nlohmann](https://github.com/nlohmann))
|
||||
- fixes \#223 by updating README.md [\#227](https://github.com/nlohmann/json/pull/227) ([kevin--](https://github.com/kevin--))
|
||||
- Use namespace std for int64\_t and uint64\_t [\#226](https://github.com/nlohmann/json/pull/226) ([lv-zheng](https://github.com/lv-zheng))
|
||||
|
|
23
README.md
23
README.md
|
@ -48,6 +48,8 @@ Though it's 2016 already, the support for C++11 is still a bit sparse. Currently
|
|||
- Clang 3.4 - 3.9 (and possibly later)
|
||||
- Microsoft Visual C++ 14.0 RC (and possibly later)
|
||||
|
||||
The code is currently checked with Travis for GCC 4.9, GCC
|
||||
|
||||
I would be happy to learn about other compilers/versions.
|
||||
|
||||
Please note:
|
||||
|
@ -63,6 +65,25 @@ Please note:
|
|||
|
||||
- For GCC running on MinGW or Android SDK, the error `'to_string' is not a member of 'std'` (or similarly, for `strtod`) may occur. Note this is not an issue with the code, but rather with the compiler itself. On Android, see above to build with a newer environment. For MinGW, please refer to [this site](http://tehsausage.com/mingw-to-string) and [this discussion](https://github.com/nlohmann/json/issues/136) for information on how to fix this bug.
|
||||
|
||||
The following compilers are currently used in [continuous integration](https://travis-ci.org/nlohmann/json):
|
||||
|
||||
| Compiler | Operating System | Version String |
|
||||
|-----------------|------------------------------|----------------|
|
||||
| GCC 4.9.3 | Ubuntu 14.04.3 LTS | g++-4.9 (Ubuntu 4.9.3-8ubuntu2~14.04) 4.9.3 |
|
||||
| GCC 5.3.0 | Ubuntu 14.04.3 LTS | g++-5 (Ubuntu 5.3.0-3ubuntu1~14.04) 5.3.0 20151204 |
|
||||
| Clang 3.6.2 | Ubuntu 14.04.3 LTS | Ubuntu clang version 3.6.2-svn240577-1~exp1 (branches/release_36) (based on LLVM 3.6.2) |
|
||||
| Clang 3.7.1 | Ubuntu 14.04.3 LTS | Ubuntu clang version 3.7.1-svn253571-1~exp1 (branches/release_37) (based on LLVM 3.7.1) |
|
||||
| Clang 3.8.1 | Ubuntu 14.04.3 LTS | clang version 3.8.1-svn265380-1~exp1 (branches/release_38) |
|
||||
| Clang 3.9.0 | Ubuntu 14.04.3 LTS | clang version 3.9.0-svn267478-1~exp1 (trunk) |
|
||||
| Clang Xcode 6.1 | Darwin Kernel Version 13.4.0 | Apple LLVM version 6.0 (clang-600.0.54) (based on LLVM 3.5svn) |
|
||||
| Clang Xcode 6.2 | Darwin Kernel Version 13.4.0 | Apple LLVM version 6.0 (clang-600.0.57) (based on LLVM 3.5svn) |
|
||||
| Clang Xcode 6.3 | Darwin Kernel Version 14.3.0 | Apple LLVM version 6.1.0 (clang-602.0.49) (based on LLVM 3.6.0svn) |
|
||||
| Clang Xcode 6.4 | Darwin Kernel Version 14.3.0 | Apple LLVM version 6.1.0 (clang-602.0.53) (based on LLVM 3.6.0svn) |
|
||||
| Clang Xcode 7.1 | Darwin Kernel Version 14.5.0 | Apple LLVM version 7.0.0 (clang-700.1.76) |
|
||||
| Clang Xcode 7.2 | Darwin Kernel Version 15.0.0 | Apple LLVM version 7.0.2 (clang-700.1.81) |
|
||||
| Clang Xcode 7.3 | Darwin Kernel Version 14.5.0 | Apple LLVM version 7.3.0 (clang-703.0.29) |
|
||||
| Visual Studio 14 2015 | Windows Server 2012 R2 (x64) | Microsoft (R) Build Engine version 14.0.25123.0 |
|
||||
|
||||
## Examples
|
||||
|
||||
Here are some examples to give you an idea how to use the class.
|
||||
|
@ -428,7 +449,7 @@ $ make
|
|||
$ ./json_unit "*"
|
||||
|
||||
===============================================================================
|
||||
All tests passed (3344416 assertions in 30 test cases)
|
||||
All tests passed (5568703 assertions in 31 test cases)
|
||||
```
|
||||
|
||||
For more information, have a look at the file [.travis.yml](https://github.com/nlohmann/json/blob/master/.travis.yml).
|
||||
|
|
34
doc/examples/diff.cpp
Normal file
34
doc/examples/diff.cpp
Normal file
|
@ -0,0 +1,34 @@
|
|||
#include <json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
int main()
|
||||
{
|
||||
// the source document
|
||||
json source = R"(
|
||||
{
|
||||
"baz": "qux",
|
||||
"foo": "bar"
|
||||
}
|
||||
)"_json;
|
||||
|
||||
// the target document
|
||||
json target = R"(
|
||||
{
|
||||
"baz": "boo",
|
||||
"hello": [
|
||||
"world"
|
||||
]
|
||||
}
|
||||
)"_json;
|
||||
|
||||
// create the patch
|
||||
json patch = json::diff(source, target);
|
||||
|
||||
// roundtrip
|
||||
json patched_source = source.patch(patch);
|
||||
|
||||
// output patch and roundtrip result
|
||||
std::cout << std::setw(4) << patch << "\n\n"
|
||||
<< std::setw(4) << patched_source << std::endl;
|
||||
}
|
1
doc/examples/diff.link
Normal file
1
doc/examples/diff.link
Normal file
|
@ -0,0 +1 @@
|
|||
<a target="_blank" href="http://melpon.org/wandbox/permlink/hicmeOK39tBxaluM"><b>online</b></a>
|
25
doc/examples/diff.output
Normal file
25
doc/examples/diff.output
Normal file
|
@ -0,0 +1,25 @@
|
|||
[
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/baz",
|
||||
"value": "boo"
|
||||
},
|
||||
{
|
||||
"op": "remove",
|
||||
"path": "/foo"
|
||||
},
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/hello",
|
||||
"value": [
|
||||
"world"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
{
|
||||
"baz": "boo",
|
||||
"hello": [
|
||||
"world"
|
||||
]
|
||||
}
|
|
@ -20,18 +20,11 @@ int main()
|
|||
{
|
||||
"object", {
|
||||
{"currency", "USD"},
|
||||
{"value", 42.99},
|
||||
{"", "empty string"},
|
||||
{"/", "slash"},
|
||||
{"~", "tilde"},
|
||||
{"~1", "tilde1"}
|
||||
{"value", 42.99}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// call flatten()
|
||||
std::cout << std::setw(4) << j.flatten() << '\n';
|
||||
|
||||
// flatten for a primitive value
|
||||
std::cout << j["pi"].flatten() << '\n';
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
<a target="_blank" href="http://melpon.org/wandbox/permlink/skGi8b32VhI8HOgV"><b>online</b></a>
|
||||
<a target="_blank" href="http://melpon.org/wandbox/permlink/OZG9sHcJCXsScNiy"><b>online</b></a>
|
|
@ -6,12 +6,7 @@
|
|||
"/list/2": 2,
|
||||
"/name": "Niels",
|
||||
"/nothing": null,
|
||||
"/object/": "empty string",
|
||||
"/object/currency": "USD",
|
||||
"/object/value": 42.99,
|
||||
"/object/~0": "tilde",
|
||||
"/object/~01": "tilde1",
|
||||
"/object/~1": "slash",
|
||||
"/pi": 3.141
|
||||
}
|
||||
{"":3.141}
|
||||
|
|
34
doc/examples/json_pointer__to_string.cpp
Normal file
34
doc/examples/json_pointer__to_string.cpp
Normal file
|
@ -0,0 +1,34 @@
|
|||
#include <json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
int main()
|
||||
{
|
||||
// different JSON Pointers
|
||||
json::json_pointer ptr1("");
|
||||
json::json_pointer ptr2("/foo");
|
||||
json::json_pointer ptr3("/foo/0");
|
||||
json::json_pointer ptr4("/");
|
||||
json::json_pointer ptr5("/a~1b");
|
||||
json::json_pointer ptr6("/c%d");
|
||||
json::json_pointer ptr7("/e^f");
|
||||
json::json_pointer ptr8("/g|h");
|
||||
json::json_pointer ptr9("/i\\j");
|
||||
json::json_pointer ptr10("/k\"l");
|
||||
json::json_pointer ptr11("/ ");
|
||||
json::json_pointer ptr12("/m~0n");
|
||||
|
||||
|
||||
std::cout << ptr1.to_string() << '\n'
|
||||
<< ptr2.to_string() << '\n'
|
||||
<< ptr3.to_string() << '\n'
|
||||
<< ptr4.to_string() << '\n'
|
||||
<< ptr5.to_string() << '\n'
|
||||
<< ptr6.to_string() << '\n'
|
||||
<< ptr7.to_string() << '\n'
|
||||
<< ptr8.to_string() << '\n'
|
||||
<< ptr9.to_string() << '\n'
|
||||
<< ptr10.to_string() << '\n'
|
||||
<< ptr11.to_string() << '\n'
|
||||
<< ptr12.to_string() << std::endl;
|
||||
}
|
1
doc/examples/json_pointer__to_string.link
Normal file
1
doc/examples/json_pointer__to_string.link
Normal file
|
@ -0,0 +1 @@
|
|||
<a target="_blank" href="http://melpon.org/wandbox/permlink/O4FbKx0TbZioFhfU"><b>online</b></a>
|
12
doc/examples/json_pointer__to_string.output
Normal file
12
doc/examples/json_pointer__to_string.output
Normal file
|
@ -0,0 +1,12 @@
|
|||
|
||||
/foo
|
||||
/foo/0
|
||||
/
|
||||
/a~1b
|
||||
/c%d
|
||||
/e^f
|
||||
/g|h
|
||||
/i\j
|
||||
/k"l
|
||||
/
|
||||
/m~0n
|
30
doc/examples/patch.cpp
Normal file
30
doc/examples/patch.cpp
Normal file
|
@ -0,0 +1,30 @@
|
|||
#include <json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
int main()
|
||||
{
|
||||
// the original document
|
||||
json doc = R"(
|
||||
{
|
||||
"baz": "qux",
|
||||
"foo": "bar"
|
||||
}
|
||||
)"_json;
|
||||
|
||||
// the patch
|
||||
json patch = R"(
|
||||
[
|
||||
{ "op": "replace", "path": "/baz", "value": "boo" },
|
||||
{ "op": "add", "path": "/hello", "value": ["world"] },
|
||||
{ "op": "remove", "path": "/foo"}
|
||||
]
|
||||
)"_json;
|
||||
|
||||
// apply the patch
|
||||
json patched_doc = doc.patch(patch);
|
||||
|
||||
// output original and patched document
|
||||
std::cout << std::setw(4) << doc << "\n\n"
|
||||
<< std::setw(4) << patched_doc << std::endl;
|
||||
}
|
1
doc/examples/patch.link
Normal file
1
doc/examples/patch.link
Normal file
|
@ -0,0 +1 @@
|
|||
<a target="_blank" href="http://melpon.org/wandbox/permlink/lbczW3AzcUbH1Nbo"><b>online</b></a>
|
11
doc/examples/patch.output
Normal file
11
doc/examples/patch.output
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"baz": "qux",
|
||||
"foo": "bar"
|
||||
}
|
||||
|
||||
{
|
||||
"baz": "boo",
|
||||
"hello": [
|
||||
"world"
|
||||
]
|
||||
}
|
|
@ -14,12 +14,8 @@ int main()
|
|||
{"/list/2", 2},
|
||||
{"/name", "Niels"},
|
||||
{"/nothing", nullptr},
|
||||
{"/object/", "empty string"},
|
||||
{"/object/currency", "USD"},
|
||||
{"/object/value", 42.99},
|
||||
{"/object/~0", "tilde"},
|
||||
{"/object/~01", "tilde1"},
|
||||
{"/object/~1", "slash"},
|
||||
{"/pi", 3.141}
|
||||
};
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
<a target="_blank" href="http://melpon.org/wandbox/permlink/ITqCZsXmi0I7KGYy"><b>online</b></a>
|
||||
<a target="_blank" href="http://melpon.org/wandbox/permlink/m2UmItixDxnYs1Se"><b>online</b></a>
|
|
@ -11,12 +11,8 @@
|
|||
"name": "Niels",
|
||||
"nothing": null,
|
||||
"object": {
|
||||
"": "empty string",
|
||||
"/": "slash",
|
||||
"currency": "USD",
|
||||
"value": 42.99,
|
||||
"~": "tilde",
|
||||
"~1": "tilde1"
|
||||
"value": 42.99
|
||||
},
|
||||
"pi": 3.141
|
||||
}
|
||||
|
|
828
src/json.hpp
828
src/json.hpp
|
@ -3595,121 +3595,6 @@ class basic_json
|
|||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@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 std::out_of_range if the JSON pointer can not be resolved
|
||||
@throw std::domain_error if an array index begins with '0'
|
||||
@throw std::invalid_argument if an array index was not a number
|
||||
|
||||
@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 std::out_of_range if the JSON pointer can not be resolved
|
||||
@throw std::domain_error if an array index begins with '0'
|
||||
@throw std::invalid_argument if an array index was not a number
|
||||
|
||||
@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
|
||||
|
||||
@complexity Constant.
|
||||
|
||||
@throw std::out_of_range if the JSON pointer can not be resolved
|
||||
@throw std::domain_error if an array index begins with '0'
|
||||
@throw std::invalid_argument if an array index was not a number
|
||||
|
||||
@liveexample{The behavior is shown in the example.,at_json_pointer}
|
||||
|
||||
@since version 2.0.0
|
||||
*/
|
||||
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.
|
||||
|
||||
@param[in] ptr JSON pointer to the desired element
|
||||
|
||||
@return reference to the element pointed to by @a ptr
|
||||
|
||||
@complexity Constant.
|
||||
|
||||
@throw std::out_of_range if the JSON pointer can not be resolved
|
||||
@throw std::domain_error if an array index begins with '0'
|
||||
@throw std::invalid_argument if an array index was not a number
|
||||
|
||||
@liveexample{The behavior is shown in the example.,at_json_pointer_const}
|
||||
|
||||
@since version 2.0.0
|
||||
*/
|
||||
const_reference at(const json_pointer& ptr) const
|
||||
{
|
||||
return ptr.get_checked(this);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief access specified object element with default value
|
||||
|
||||
|
@ -4145,8 +4030,8 @@ class basic_json
|
|||
|
||||
@throw std::domain_error when called on a type other than JSON array;
|
||||
example: `"cannot use erase() with null"`
|
||||
@throw std::out_of_range when `idx >= size()`; example: `"index out of
|
||||
range"`
|
||||
@throw std::out_of_range when `idx >= size()`; example: `"array index 17
|
||||
is out of range"`
|
||||
|
||||
@complexity Linear in distance between @a idx and the end of the container.
|
||||
|
||||
|
@ -4167,7 +4052,7 @@ class basic_json
|
|||
{
|
||||
if (idx >= size())
|
||||
{
|
||||
throw std::out_of_range("index out of range");
|
||||
throw std::out_of_range("array index " + std::to_string(idx) + " is out of range");
|
||||
}
|
||||
|
||||
assert(m_value.array != nullptr);
|
||||
|
@ -8933,6 +8818,10 @@ basic_json_parser_63:
|
|||
/*!
|
||||
@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
|
||||
|
@ -8969,7 +8858,71 @@ basic_json_parser_63:
|
|||
: 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
|
||||
{
|
||||
std::string result;
|
||||
|
||||
for (const auto& reference_token : reference_tokens)
|
||||
{
|
||||
result += "/" + escape(reference_token);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// @copydoc to_string()
|
||||
operator std::string() const
|
||||
{
|
||||
return to_string();
|
||||
}
|
||||
|
||||
private:
|
||||
/// remove and return last reference pointer
|
||||
std::string pop_back()
|
||||
{
|
||||
if (is_root())
|
||||
{
|
||||
throw std::domain_error("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())
|
||||
{
|
||||
throw std::domain_error("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
|
||||
*/
|
||||
|
@ -9008,7 +8961,7 @@ basic_json_parser_63:
|
|||
case value_t::array:
|
||||
{
|
||||
// create an entry in the array
|
||||
result = &result->operator[](static_cast<size_t>(std::stoi(reference_token)));
|
||||
result = &result->operator[](static_cast<size_type>(std::stoi(reference_token)));
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -9071,7 +9024,7 @@ basic_json_parser_63:
|
|||
else
|
||||
{
|
||||
// convert array index to number; unchecked access
|
||||
ptr = &ptr->operator[](static_cast<size_t>(std::stoi(reference_token)));
|
||||
ptr = &ptr->operator[](static_cast<size_type>(std::stoi(reference_token)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -9116,7 +9069,7 @@ basic_json_parser_63:
|
|||
}
|
||||
|
||||
// note: at performs range check
|
||||
ptr = &ptr->at(static_cast<size_t>(std::stoi(reference_token)));
|
||||
ptr = &ptr->at(static_cast<size_type>(std::stoi(reference_token)));
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -9168,7 +9121,7 @@ basic_json_parser_63:
|
|||
}
|
||||
|
||||
// use unchecked array access
|
||||
ptr = &ptr->operator[](static_cast<size_t>(std::stoi(reference_token)));
|
||||
ptr = &ptr->operator[](static_cast<size_type>(std::stoi(reference_token)));
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -9212,7 +9165,7 @@ basic_json_parser_63:
|
|||
}
|
||||
|
||||
// note: at performs range check
|
||||
ptr = &ptr->at(static_cast<size_t>(std::stoi(reference_token)));
|
||||
ptr = &ptr->at(static_cast<size_type>(std::stoi(reference_token)));
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -9279,12 +9232,8 @@ basic_json_parser_63:
|
|||
}
|
||||
}
|
||||
|
||||
// first transform any occurrence of the sequence '~1' to '/'
|
||||
replace_substring(reference_token, "~1", "/");
|
||||
// then transform any occurrence of the sequence '~0' to '~'
|
||||
replace_substring(reference_token, "~0", "~");
|
||||
|
||||
// finally, store the reference token
|
||||
unescape(reference_token);
|
||||
result.push_back(reference_token);
|
||||
}
|
||||
|
||||
|
@ -9320,6 +9269,24 @@ basic_json_parser_63:
|
|||
);
|
||||
}
|
||||
|
||||
/// 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
|
||||
|
@ -9327,7 +9294,7 @@ basic_json_parser_63:
|
|||
|
||||
@note Empty objects or arrays are flattened to `null`.
|
||||
*/
|
||||
static void flatten(const std::string reference_string,
|
||||
static void flatten(const std::string& reference_string,
|
||||
const basic_json& value,
|
||||
basic_json& result)
|
||||
{
|
||||
|
@ -9364,12 +9331,7 @@ basic_json_parser_63:
|
|||
// iterate object and use keys as reference string
|
||||
for (const auto& element : *value.m_value.object)
|
||||
{
|
||||
// escape "~"" to "~0" and "/" to "~1"
|
||||
std::string key(element.first);
|
||||
replace_substring(key, "~", "~0");
|
||||
replace_substring(key, "/", "~1");
|
||||
|
||||
flatten(reference_string + "/" + key,
|
||||
flatten(reference_string + "/" + escape(element.first),
|
||||
element.second, result);
|
||||
}
|
||||
}
|
||||
|
@ -9420,16 +9382,131 @@ basic_json_parser_63:
|
|||
|
||||
private:
|
||||
/// the reference tokens
|
||||
const std::vector<std::string> reference_tokens {};
|
||||
std::vector<std::string> reference_tokens {};
|
||||
};
|
||||
|
||||
////////////////////////////
|
||||
// JSON Pointer functions //
|
||||
////////////////////////////
|
||||
//////////////////////////
|
||||
// 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 std::out_of_range if the JSON pointer can not be resolved
|
||||
@throw std::domain_error if an array index begins with '0'
|
||||
@throw std::invalid_argument if an array index was not a number
|
||||
|
||||
@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 std::out_of_range if the JSON pointer can not be resolved
|
||||
@throw std::domain_error if an array index begins with '0'
|
||||
@throw std::invalid_argument if an array index was not a number
|
||||
|
||||
@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
|
||||
|
||||
@complexity Constant.
|
||||
|
||||
@throw std::out_of_range if the JSON pointer can not be resolved
|
||||
@throw std::domain_error if an array index begins with '0'
|
||||
@throw std::invalid_argument if an array index was not a number
|
||||
|
||||
@liveexample{The behavior is shown in the example.,at_json_pointer}
|
||||
|
||||
@since version 2.0.0
|
||||
*/
|
||||
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.
|
||||
|
||||
@param[in] ptr JSON pointer to the desired element
|
||||
|
||||
@return reference to the element pointed to by @a ptr
|
||||
|
||||
@complexity Constant.
|
||||
|
||||
@throw std::out_of_range if the JSON pointer can not be resolved
|
||||
@throw std::domain_error if an array index begins with '0'
|
||||
@throw std::invalid_argument if an array index was not a number
|
||||
|
||||
@liveexample{The behavior is shown in the example.,at_json_pointer_const}
|
||||
|
||||
@since version 2.0.0
|
||||
*/
|
||||
const_reference at(const json_pointer& ptr) const
|
||||
{
|
||||
return ptr.get_checked(this);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief return flattened JSON value
|
||||
|
||||
|
@ -9492,6 +9569,473 @@ basic_json_parser_63:
|
|||
}
|
||||
|
||||
/// @}
|
||||
|
||||
//////////////////////////
|
||||
// JSON Patch functions //
|
||||
//////////////////////////
|
||||
|
||||
/// @name JSON Patch functions
|
||||
/// @{
|
||||
|
||||
/*!
|
||||
@brief applies a JSON patch
|
||||
|
||||
[JSON Patch](http://jsonpatch.com) defines a JSON document structure for
|
||||
expressing a sequence of operations to apply to a JSON) document. With
|
||||
this funcion, a JSON Patch is applied to the current JSON value by
|
||||
executing all operations from the patch.
|
||||
|
||||
@param[in] patch JSON patch document
|
||||
@return patched document
|
||||
|
||||
@note The application of a patch is atomic: Either all operations succeed
|
||||
and the patched document is returned or an exception is thrown. In
|
||||
any case, the original value is not changed: the patch is applied
|
||||
to a copy of the value.
|
||||
|
||||
@throw std::out_of_range if a JSON pointer inside the patch could not
|
||||
be resolved successfully in the current JSON value; example: `"key baz
|
||||
not found"`
|
||||
@throw invalid_argument if the JSON patch is malformed (e.g., mandatory
|
||||
attributes are missing); example: `"operation add must have member path"`
|
||||
|
||||
@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
|
||||
the patch, the complexity can usually be neglected.
|
||||
|
||||
@liveexample{The following code shows how a JSON patch is applied to a
|
||||
value.,patch}
|
||||
|
||||
@sa @ref diff -- create a JSON patch by comparing two JSON values
|
||||
|
||||
@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
|
||||
*/
|
||||
basic_json patch(const basic_json& patch) const
|
||||
{
|
||||
// make a working copy to apply the patch to
|
||||
basic_json result = *this;
|
||||
|
||||
// the valid JSON Patch operations
|
||||
enum class patch_operations {add, remove, replace, move, copy, test, invalid};
|
||||
|
||||
const auto get_op = [](const std::string op)
|
||||
{
|
||||
if (op == "add")
|
||||
{
|
||||
return patch_operations::add;
|
||||
}
|
||||
if (op == "remove")
|
||||
{
|
||||
return patch_operations::remove;
|
||||
}
|
||||
if (op == "replace")
|
||||
{
|
||||
return patch_operations::replace;
|
||||
}
|
||||
if (op == "move")
|
||||
{
|
||||
return patch_operations::move;
|
||||
}
|
||||
if (op == "copy")
|
||||
{
|
||||
return patch_operations::copy;
|
||||
}
|
||||
if (op == "test")
|
||||
{
|
||||
return patch_operations::test;
|
||||
}
|
||||
|
||||
return patch_operations::invalid;
|
||||
};
|
||||
|
||||
// wrapper for "add" operation; add value at ptr
|
||||
const auto operation_add = [&result](json_pointer & ptr, basic_json val)
|
||||
{
|
||||
// adding to the root of the target document means replacing it
|
||||
if (ptr.is_root())
|
||||
{
|
||||
result = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
// make sure the top element of the pointer exists
|
||||
json_pointer top_pointer = ptr.top();
|
||||
if (top_pointer != ptr)
|
||||
{
|
||||
basic_json& x = 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;
|
||||
}
|
||||
|
||||
case value_t::array:
|
||||
{
|
||||
if (last_path == "-")
|
||||
{
|
||||
// special case: append to back
|
||||
parent.push_back(val);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto idx = std::stoi(last_path);
|
||||
if (static_cast<size_type>(idx) > parent.size())
|
||||
{
|
||||
// avoid undefined behavior
|
||||
throw std::out_of_range("array index " + std::to_string(idx) + " is out of range");
|
||||
}
|
||||
else
|
||||
{
|
||||
// default case: insert add offset
|
||||
parent.insert(parent.begin() + static_cast<difference_type>(idx), val);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
// if there exists a parent it cannot be primitive
|
||||
assert(false); // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// wrapper for "remove" operation; remove value at 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
|
||||
if (parent.is_object())
|
||||
{
|
||||
// perform range check
|
||||
auto it = parent.find(last_path);
|
||||
if (it != parent.end())
|
||||
{
|
||||
parent.erase(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::out_of_range("key '" + last_path + "' not found");
|
||||
}
|
||||
}
|
||||
else if (parent.is_array())
|
||||
{
|
||||
// note erase performs range check
|
||||
parent.erase(static_cast<size_type>(std::stoi(last_path)));
|
||||
}
|
||||
};
|
||||
|
||||
// type check
|
||||
if (not patch.is_array())
|
||||
{
|
||||
// a JSON patch must be an array of objects
|
||||
throw std::invalid_argument("JSON patch must be an array of objects");
|
||||
}
|
||||
|
||||
// iterate and apply th eoperations
|
||||
for (const auto& val : 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())
|
||||
{
|
||||
throw std::invalid_argument(error_msg + " must have member '" + member + "'");
|
||||
}
|
||||
|
||||
// check if result is of type string
|
||||
if (string_type and not it->second.is_string())
|
||||
{
|
||||
throw std::invalid_argument(error_msg + " must have string member '" + member + "'");
|
||||
}
|
||||
|
||||
// no error: return value
|
||||
return it->second;
|
||||
};
|
||||
|
||||
// type check
|
||||
if (not val.is_object())
|
||||
{
|
||||
throw std::invalid_argument("JSON patch must be an array of objects");
|
||||
}
|
||||
|
||||
// collect mandatory members
|
||||
const std::string op = get_value("op", "op", true);
|
||||
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));
|
||||
break;
|
||||
}
|
||||
|
||||
case patch_operations::remove:
|
||||
{
|
||||
operation_remove(ptr);
|
||||
break;
|
||||
}
|
||||
|
||||
case patch_operations::replace:
|
||||
{
|
||||
// the "path" location must exist - use at()
|
||||
result.at(ptr) = get_value("replace", "value", false);
|
||||
break;
|
||||
}
|
||||
|
||||
case patch_operations::move:
|
||||
{
|
||||
const std::string from_path = get_value("move", "from", true);
|
||||
json_pointer from_ptr(from_path);
|
||||
|
||||
// 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:
|
||||
{
|
||||
const std::string from_path = get_value("copy", "from", true);;
|
||||
const json_pointer from_ptr(from_path);
|
||||
|
||||
// the "from" location must exist - use at()
|
||||
result[ptr] = result.at(from_ptr);
|
||||
break;
|
||||
}
|
||||
|
||||
case patch_operations::test:
|
||||
{
|
||||
bool success = false;
|
||||
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));
|
||||
}
|
||||
catch (std::out_of_range&)
|
||||
{
|
||||
// ignore out of range errors: success remains false
|
||||
}
|
||||
|
||||
// throw an exception if test fails
|
||||
if (not success)
|
||||
{
|
||||
throw std::domain_error("unsuccessful: " + val.dump());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case patch_operations::invalid:
|
||||
{
|
||||
// op must be "add", "remove", "replace", "move", "copy", or
|
||||
// "test"
|
||||
throw std::invalid_argument("operation value '" + op + "' is invalid");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
@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 copare from
|
||||
@param[in] target JSON value to copare 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
|
||||
*/
|
||||
static basic_json diff(const basic_json& source,
|
||||
const basic_json& target,
|
||||
std::string path = "") noexcept
|
||||
{
|
||||
// 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())
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
// i now reached the end of at least one array
|
||||
// in a second pass, traverse the remaining elements
|
||||
|
||||
// remove my remaining elements
|
||||
while (i < source.size())
|
||||
{
|
||||
result.push_back(object(
|
||||
{
|
||||
{"op", "remove"},
|
||||
{"path", path + "/" + std::to_string(i)}
|
||||
}));
|
||||
++i;
|
||||
}
|
||||
|
||||
// add other remaining elements
|
||||
while (i < target.size())
|
||||
{
|
||||
result.push_back(
|
||||
{
|
||||
{"op", "add"},
|
||||
{"path", path + "/" + std::to_string(i)},
|
||||
{"value", target[i]}
|
||||
});
|
||||
++i;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::object:
|
||||
{
|
||||
// first pass: traverse this object's elements
|
||||
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());
|
||||
|
||||
if (target.find(it.key()) != target.end())
|
||||
{
|
||||
// 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}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// second pass: traverse other object's elements
|
||||
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()}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
// both primitive type: replace value
|
||||
result.push_back(
|
||||
{
|
||||
{"op", "replace"},
|
||||
{"path", path},
|
||||
{"value", target}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// @}
|
||||
};
|
||||
|
||||
|
||||
|
@ -9511,9 +10055,9 @@ using json = basic_json<>;
|
|||
}
|
||||
|
||||
|
||||
/////////////////////////
|
||||
// nonmember functions //
|
||||
/////////////////////////
|
||||
///////////////////////
|
||||
// nonmember support //
|
||||
///////////////////////
|
||||
|
||||
// specialization of std::swap, and std::hash
|
||||
namespace std
|
||||
|
|
|
@ -3595,121 +3595,6 @@ class basic_json
|
|||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@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 std::out_of_range if the JSON pointer can not be resolved
|
||||
@throw std::domain_error if an array index begins with '0'
|
||||
@throw std::invalid_argument if an array index was not a number
|
||||
|
||||
@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 std::out_of_range if the JSON pointer can not be resolved
|
||||
@throw std::domain_error if an array index begins with '0'
|
||||
@throw std::invalid_argument if an array index was not a number
|
||||
|
||||
@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
|
||||
|
||||
@complexity Constant.
|
||||
|
||||
@throw std::out_of_range if the JSON pointer can not be resolved
|
||||
@throw std::domain_error if an array index begins with '0'
|
||||
@throw std::invalid_argument if an array index was not a number
|
||||
|
||||
@liveexample{The behavior is shown in the example.,at_json_pointer}
|
||||
|
||||
@since version 2.0.0
|
||||
*/
|
||||
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.
|
||||
|
||||
@param[in] ptr JSON pointer to the desired element
|
||||
|
||||
@return reference to the element pointed to by @a ptr
|
||||
|
||||
@complexity Constant.
|
||||
|
||||
@throw std::out_of_range if the JSON pointer can not be resolved
|
||||
@throw std::domain_error if an array index begins with '0'
|
||||
@throw std::invalid_argument if an array index was not a number
|
||||
|
||||
@liveexample{The behavior is shown in the example.,at_json_pointer_const}
|
||||
|
||||
@since version 2.0.0
|
||||
*/
|
||||
const_reference at(const json_pointer& ptr) const
|
||||
{
|
||||
return ptr.get_checked(this);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief access specified object element with default value
|
||||
|
||||
|
@ -4145,8 +4030,8 @@ class basic_json
|
|||
|
||||
@throw std::domain_error when called on a type other than JSON array;
|
||||
example: `"cannot use erase() with null"`
|
||||
@throw std::out_of_range when `idx >= size()`; example: `"index out of
|
||||
range"`
|
||||
@throw std::out_of_range when `idx >= size()`; example: `"array index 17
|
||||
is out of range"`
|
||||
|
||||
@complexity Linear in distance between @a idx and the end of the container.
|
||||
|
||||
|
@ -4167,7 +4052,7 @@ class basic_json
|
|||
{
|
||||
if (idx >= size())
|
||||
{
|
||||
throw std::out_of_range("index out of range");
|
||||
throw std::out_of_range("array index " + std::to_string(idx) + " is out of range");
|
||||
}
|
||||
|
||||
assert(m_value.array != nullptr);
|
||||
|
@ -8243,6 +8128,10 @@ class basic_json
|
|||
/*!
|
||||
@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
|
||||
|
@ -8279,7 +8168,71 @@ class basic_json
|
|||
: 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
|
||||
{
|
||||
std::string result;
|
||||
|
||||
for (const auto& reference_token : reference_tokens)
|
||||
{
|
||||
result += "/" + escape(reference_token);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// @copydoc to_string()
|
||||
operator std::string() const
|
||||
{
|
||||
return to_string();
|
||||
}
|
||||
|
||||
private:
|
||||
/// remove and return last reference pointer
|
||||
std::string pop_back()
|
||||
{
|
||||
if (is_root())
|
||||
{
|
||||
throw std::domain_error("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())
|
||||
{
|
||||
throw std::domain_error("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
|
||||
*/
|
||||
|
@ -8318,7 +8271,7 @@ class basic_json
|
|||
case value_t::array:
|
||||
{
|
||||
// create an entry in the array
|
||||
result = &result->operator[](static_cast<size_t>(std::stoi(reference_token)));
|
||||
result = &result->operator[](static_cast<size_type>(std::stoi(reference_token)));
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -8381,7 +8334,7 @@ class basic_json
|
|||
else
|
||||
{
|
||||
// convert array index to number; unchecked access
|
||||
ptr = &ptr->operator[](static_cast<size_t>(std::stoi(reference_token)));
|
||||
ptr = &ptr->operator[](static_cast<size_type>(std::stoi(reference_token)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -8426,7 +8379,7 @@ class basic_json
|
|||
}
|
||||
|
||||
// note: at performs range check
|
||||
ptr = &ptr->at(static_cast<size_t>(std::stoi(reference_token)));
|
||||
ptr = &ptr->at(static_cast<size_type>(std::stoi(reference_token)));
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -8478,7 +8431,7 @@ class basic_json
|
|||
}
|
||||
|
||||
// use unchecked array access
|
||||
ptr = &ptr->operator[](static_cast<size_t>(std::stoi(reference_token)));
|
||||
ptr = &ptr->operator[](static_cast<size_type>(std::stoi(reference_token)));
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -8522,7 +8475,7 @@ class basic_json
|
|||
}
|
||||
|
||||
// note: at performs range check
|
||||
ptr = &ptr->at(static_cast<size_t>(std::stoi(reference_token)));
|
||||
ptr = &ptr->at(static_cast<size_type>(std::stoi(reference_token)));
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -8589,12 +8542,8 @@ class basic_json
|
|||
}
|
||||
}
|
||||
|
||||
// first transform any occurrence of the sequence '~1' to '/'
|
||||
replace_substring(reference_token, "~1", "/");
|
||||
// then transform any occurrence of the sequence '~0' to '~'
|
||||
replace_substring(reference_token, "~0", "~");
|
||||
|
||||
// finally, store the reference token
|
||||
unescape(reference_token);
|
||||
result.push_back(reference_token);
|
||||
}
|
||||
|
||||
|
@ -8630,6 +8579,24 @@ class basic_json
|
|||
);
|
||||
}
|
||||
|
||||
/// 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
|
||||
|
@ -8637,7 +8604,7 @@ class basic_json
|
|||
|
||||
@note Empty objects or arrays are flattened to `null`.
|
||||
*/
|
||||
static void flatten(const std::string reference_string,
|
||||
static void flatten(const std::string& reference_string,
|
||||
const basic_json& value,
|
||||
basic_json& result)
|
||||
{
|
||||
|
@ -8674,12 +8641,7 @@ class basic_json
|
|||
// iterate object and use keys as reference string
|
||||
for (const auto& element : *value.m_value.object)
|
||||
{
|
||||
// escape "~"" to "~0" and "/" to "~1"
|
||||
std::string key(element.first);
|
||||
replace_substring(key, "~", "~0");
|
||||
replace_substring(key, "/", "~1");
|
||||
|
||||
flatten(reference_string + "/" + key,
|
||||
flatten(reference_string + "/" + escape(element.first),
|
||||
element.second, result);
|
||||
}
|
||||
}
|
||||
|
@ -8730,16 +8692,131 @@ class basic_json
|
|||
|
||||
private:
|
||||
/// the reference tokens
|
||||
const std::vector<std::string> reference_tokens {};
|
||||
std::vector<std::string> reference_tokens {};
|
||||
};
|
||||
|
||||
////////////////////////////
|
||||
// JSON Pointer functions //
|
||||
////////////////////////////
|
||||
//////////////////////////
|
||||
// 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 std::out_of_range if the JSON pointer can not be resolved
|
||||
@throw std::domain_error if an array index begins with '0'
|
||||
@throw std::invalid_argument if an array index was not a number
|
||||
|
||||
@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 std::out_of_range if the JSON pointer can not be resolved
|
||||
@throw std::domain_error if an array index begins with '0'
|
||||
@throw std::invalid_argument if an array index was not a number
|
||||
|
||||
@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
|
||||
|
||||
@complexity Constant.
|
||||
|
||||
@throw std::out_of_range if the JSON pointer can not be resolved
|
||||
@throw std::domain_error if an array index begins with '0'
|
||||
@throw std::invalid_argument if an array index was not a number
|
||||
|
||||
@liveexample{The behavior is shown in the example.,at_json_pointer}
|
||||
|
||||
@since version 2.0.0
|
||||
*/
|
||||
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.
|
||||
|
||||
@param[in] ptr JSON pointer to the desired element
|
||||
|
||||
@return reference to the element pointed to by @a ptr
|
||||
|
||||
@complexity Constant.
|
||||
|
||||
@throw std::out_of_range if the JSON pointer can not be resolved
|
||||
@throw std::domain_error if an array index begins with '0'
|
||||
@throw std::invalid_argument if an array index was not a number
|
||||
|
||||
@liveexample{The behavior is shown in the example.,at_json_pointer_const}
|
||||
|
||||
@since version 2.0.0
|
||||
*/
|
||||
const_reference at(const json_pointer& ptr) const
|
||||
{
|
||||
return ptr.get_checked(this);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief return flattened JSON value
|
||||
|
||||
|
@ -8802,6 +8879,473 @@ class basic_json
|
|||
}
|
||||
|
||||
/// @}
|
||||
|
||||
//////////////////////////
|
||||
// JSON Patch functions //
|
||||
//////////////////////////
|
||||
|
||||
/// @name JSON Patch functions
|
||||
/// @{
|
||||
|
||||
/*!
|
||||
@brief applies a JSON patch
|
||||
|
||||
[JSON Patch](http://jsonpatch.com) defines a JSON document structure for
|
||||
expressing a sequence of operations to apply to a JSON) document. With
|
||||
this funcion, a JSON Patch is applied to the current JSON value by
|
||||
executing all operations from the patch.
|
||||
|
||||
@param[in] patch JSON patch document
|
||||
@return patched document
|
||||
|
||||
@note The application of a patch is atomic: Either all operations succeed
|
||||
and the patched document is returned or an exception is thrown. In
|
||||
any case, the original value is not changed: the patch is applied
|
||||
to a copy of the value.
|
||||
|
||||
@throw std::out_of_range if a JSON pointer inside the patch could not
|
||||
be resolved successfully in the current JSON value; example: `"key baz
|
||||
not found"`
|
||||
@throw invalid_argument if the JSON patch is malformed (e.g., mandatory
|
||||
attributes are missing); example: `"operation add must have member path"`
|
||||
|
||||
@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
|
||||
the patch, the complexity can usually be neglected.
|
||||
|
||||
@liveexample{The following code shows how a JSON patch is applied to a
|
||||
value.,patch}
|
||||
|
||||
@sa @ref diff -- create a JSON patch by comparing two JSON values
|
||||
|
||||
@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
|
||||
*/
|
||||
basic_json patch(const basic_json& patch) const
|
||||
{
|
||||
// make a working copy to apply the patch to
|
||||
basic_json result = *this;
|
||||
|
||||
// the valid JSON Patch operations
|
||||
enum class patch_operations {add, remove, replace, move, copy, test, invalid};
|
||||
|
||||
const auto get_op = [](const std::string op)
|
||||
{
|
||||
if (op == "add")
|
||||
{
|
||||
return patch_operations::add;
|
||||
}
|
||||
if (op == "remove")
|
||||
{
|
||||
return patch_operations::remove;
|
||||
}
|
||||
if (op == "replace")
|
||||
{
|
||||
return patch_operations::replace;
|
||||
}
|
||||
if (op == "move")
|
||||
{
|
||||
return patch_operations::move;
|
||||
}
|
||||
if (op == "copy")
|
||||
{
|
||||
return patch_operations::copy;
|
||||
}
|
||||
if (op == "test")
|
||||
{
|
||||
return patch_operations::test;
|
||||
}
|
||||
|
||||
return patch_operations::invalid;
|
||||
};
|
||||
|
||||
// wrapper for "add" operation; add value at ptr
|
||||
const auto operation_add = [&result](json_pointer & ptr, basic_json val)
|
||||
{
|
||||
// adding to the root of the target document means replacing it
|
||||
if (ptr.is_root())
|
||||
{
|
||||
result = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
// make sure the top element of the pointer exists
|
||||
json_pointer top_pointer = ptr.top();
|
||||
if (top_pointer != ptr)
|
||||
{
|
||||
basic_json& x = 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;
|
||||
}
|
||||
|
||||
case value_t::array:
|
||||
{
|
||||
if (last_path == "-")
|
||||
{
|
||||
// special case: append to back
|
||||
parent.push_back(val);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto idx = std::stoi(last_path);
|
||||
if (static_cast<size_type>(idx) > parent.size())
|
||||
{
|
||||
// avoid undefined behavior
|
||||
throw std::out_of_range("array index " + std::to_string(idx) + " is out of range");
|
||||
}
|
||||
else
|
||||
{
|
||||
// default case: insert add offset
|
||||
parent.insert(parent.begin() + static_cast<difference_type>(idx), val);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
// if there exists a parent it cannot be primitive
|
||||
assert(false); // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// wrapper for "remove" operation; remove value at 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
|
||||
if (parent.is_object())
|
||||
{
|
||||
// perform range check
|
||||
auto it = parent.find(last_path);
|
||||
if (it != parent.end())
|
||||
{
|
||||
parent.erase(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::out_of_range("key '" + last_path + "' not found");
|
||||
}
|
||||
}
|
||||
else if (parent.is_array())
|
||||
{
|
||||
// note erase performs range check
|
||||
parent.erase(static_cast<size_type>(std::stoi(last_path)));
|
||||
}
|
||||
};
|
||||
|
||||
// type check
|
||||
if (not patch.is_array())
|
||||
{
|
||||
// a JSON patch must be an array of objects
|
||||
throw std::invalid_argument("JSON patch must be an array of objects");
|
||||
}
|
||||
|
||||
// iterate and apply th eoperations
|
||||
for (const auto& val : 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())
|
||||
{
|
||||
throw std::invalid_argument(error_msg + " must have member '" + member + "'");
|
||||
}
|
||||
|
||||
// check if result is of type string
|
||||
if (string_type and not it->second.is_string())
|
||||
{
|
||||
throw std::invalid_argument(error_msg + " must have string member '" + member + "'");
|
||||
}
|
||||
|
||||
// no error: return value
|
||||
return it->second;
|
||||
};
|
||||
|
||||
// type check
|
||||
if (not val.is_object())
|
||||
{
|
||||
throw std::invalid_argument("JSON patch must be an array of objects");
|
||||
}
|
||||
|
||||
// collect mandatory members
|
||||
const std::string op = get_value("op", "op", true);
|
||||
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));
|
||||
break;
|
||||
}
|
||||
|
||||
case patch_operations::remove:
|
||||
{
|
||||
operation_remove(ptr);
|
||||
break;
|
||||
}
|
||||
|
||||
case patch_operations::replace:
|
||||
{
|
||||
// the "path" location must exist - use at()
|
||||
result.at(ptr) = get_value("replace", "value", false);
|
||||
break;
|
||||
}
|
||||
|
||||
case patch_operations::move:
|
||||
{
|
||||
const std::string from_path = get_value("move", "from", true);
|
||||
json_pointer from_ptr(from_path);
|
||||
|
||||
// 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:
|
||||
{
|
||||
const std::string from_path = get_value("copy", "from", true);;
|
||||
const json_pointer from_ptr(from_path);
|
||||
|
||||
// the "from" location must exist - use at()
|
||||
result[ptr] = result.at(from_ptr);
|
||||
break;
|
||||
}
|
||||
|
||||
case patch_operations::test:
|
||||
{
|
||||
bool success = false;
|
||||
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));
|
||||
}
|
||||
catch (std::out_of_range&)
|
||||
{
|
||||
// ignore out of range errors: success remains false
|
||||
}
|
||||
|
||||
// throw an exception if test fails
|
||||
if (not success)
|
||||
{
|
||||
throw std::domain_error("unsuccessful: " + val.dump());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case patch_operations::invalid:
|
||||
{
|
||||
// op must be "add", "remove", "replace", "move", "copy", or
|
||||
// "test"
|
||||
throw std::invalid_argument("operation value '" + op + "' is invalid");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
@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 copare from
|
||||
@param[in] target JSON value to copare 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
|
||||
*/
|
||||
static basic_json diff(const basic_json& source,
|
||||
const basic_json& target,
|
||||
std::string path = "") noexcept
|
||||
{
|
||||
// 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())
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
// i now reached the end of at least one array
|
||||
// in a second pass, traverse the remaining elements
|
||||
|
||||
// remove my remaining elements
|
||||
while (i < source.size())
|
||||
{
|
||||
result.push_back(object(
|
||||
{
|
||||
{"op", "remove"},
|
||||
{"path", path + "/" + std::to_string(i)}
|
||||
}));
|
||||
++i;
|
||||
}
|
||||
|
||||
// add other remaining elements
|
||||
while (i < target.size())
|
||||
{
|
||||
result.push_back(
|
||||
{
|
||||
{"op", "add"},
|
||||
{"path", path + "/" + std::to_string(i)},
|
||||
{"value", target[i]}
|
||||
});
|
||||
++i;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::object:
|
||||
{
|
||||
// first pass: traverse this object's elements
|
||||
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());
|
||||
|
||||
if (target.find(it.key()) != target.end())
|
||||
{
|
||||
// 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}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// second pass: traverse other object's elements
|
||||
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()}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
// both primitive type: replace value
|
||||
result.push_back(
|
||||
{
|
||||
{"op", "replace"},
|
||||
{"path", path},
|
||||
{"value", target}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// @}
|
||||
};
|
||||
|
||||
|
||||
|
@ -8821,9 +9365,9 @@ using json = basic_json<>;
|
|||
}
|
||||
|
||||
|
||||
/////////////////////////
|
||||
// nonmember functions //
|
||||
/////////////////////////
|
||||
///////////////////////
|
||||
// nonmember support //
|
||||
///////////////////////
|
||||
|
||||
// specialization of std::swap, and std::hash
|
||||
namespace std
|
||||
|
|
1233
test/unit.cpp
1233
test/unit.cpp
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue