cleanup and documentation
This commit is contained in:
parent
f834965b44
commit
40e899a819
15 changed files with 978 additions and 202 deletions
34
doc/examples/flatten.cpp
Normal file
34
doc/examples/flatten.cpp
Normal file
|
@ -0,0 +1,34 @@
|
|||
#include <json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
int main()
|
||||
{
|
||||
// create JSON value
|
||||
json j =
|
||||
{
|
||||
{"pi", 3.141},
|
||||
{"happy", true},
|
||||
{"name", "Niels"},
|
||||
{"nothing", nullptr},
|
||||
{
|
||||
"answer", {
|
||||
{"everything", 42}
|
||||
}
|
||||
},
|
||||
{"list", {1, 0, 2}},
|
||||
{
|
||||
"object", {
|
||||
{"currency", "USD"},
|
||||
{"value", 42.99},
|
||||
{"", "empty string"},
|
||||
{"/", "slash"},
|
||||
{"~", "tilde"},
|
||||
{"~1", "tilde1"}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// call flatten()
|
||||
std::cout << std::setw(4) << j.flatten() << '\n';
|
||||
}
|
1
doc/examples/flatten.link
Normal file
1
doc/examples/flatten.link
Normal file
|
@ -0,0 +1 @@
|
|||
<a target="_blank" href="http://melpon.org/wandbox/permlink/kODXfzcksgstdBRD"><b>online</b></a>
|
16
doc/examples/flatten.output
Normal file
16
doc/examples/flatten.output
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"/answer/everything": 42,
|
||||
"/happy": true,
|
||||
"/list/0": 1,
|
||||
"/list/1": 0,
|
||||
"/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
|
||||
}
|
47
doc/examples/operatorjson_pointer.cpp
Normal file
47
doc/examples/operatorjson_pointer.cpp
Normal file
|
@ -0,0 +1,47 @@
|
|||
#include <json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
int main()
|
||||
{
|
||||
// create a JSON value
|
||||
json j =
|
||||
{
|
||||
{"number", 1}, {"string", "foo"}, {"array", {1, 2}}
|
||||
};
|
||||
|
||||
// read-only access
|
||||
|
||||
// output element with JSON pointer "/number"
|
||||
std::cout << j["/number"_json_pointer] << '\n';
|
||||
// output element with JSON pointer "/string"
|
||||
std::cout << j["/string"_json_pointer] << '\n';
|
||||
// output element with JSON pointer "/array"
|
||||
std::cout << j["/array"_json_pointer] << '\n';
|
||||
// output element with JSON pointer "/array/1"
|
||||
std::cout << j["/array/1"_json_pointer] << '\n';
|
||||
|
||||
// writing access
|
||||
|
||||
// change the string
|
||||
j["/string"_json_pointer] = "bar";
|
||||
// output the changed string
|
||||
std::cout << j["string"] << '\n';
|
||||
|
||||
// "change" a nonexisting object entry
|
||||
j["/boolean"_json_pointer] = true;
|
||||
// output the changed object
|
||||
std::cout << j << '\n';
|
||||
|
||||
// change an array element
|
||||
j["/array/1"_json_pointer] = 21;
|
||||
// "change" an array element with nonexisting index
|
||||
j["/array/4"_json_pointer] = 44;
|
||||
// output the changed array
|
||||
std::cout << j["array"] << '\n';
|
||||
|
||||
// "change" the arry element past the end
|
||||
j["/array/-"_json_pointer] = 55;
|
||||
// output the changed array
|
||||
std::cout << j["array"] << '\n';
|
||||
}
|
1
doc/examples/operatorjson_pointer.link
Normal file
1
doc/examples/operatorjson_pointer.link
Normal file
|
@ -0,0 +1 @@
|
|||
<a target="_blank" href="http://melpon.org/wandbox/permlink/6oeNnra3wjPijLSr"><b>online</b></a>
|
8
doc/examples/operatorjson_pointer.output
Normal file
8
doc/examples/operatorjson_pointer.output
Normal file
|
@ -0,0 +1,8 @@
|
|||
1
|
||||
"foo"
|
||||
[1,2]
|
||||
2
|
||||
"bar"
|
||||
{"array":[1,2],"boolean":true,"number":1,"string":"bar"}
|
||||
[1,21,null,null,44]
|
||||
[1,21,null,null,44,55]
|
23
doc/examples/operatorjson_pointer_const.cpp
Normal file
23
doc/examples/operatorjson_pointer_const.cpp
Normal file
|
@ -0,0 +1,23 @@
|
|||
#include <json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
int main()
|
||||
{
|
||||
// create a JSON value
|
||||
const json j =
|
||||
{
|
||||
{"number", 1}, {"string", "foo"}, {"array", {1, 2}}
|
||||
};
|
||||
|
||||
// read-only access
|
||||
|
||||
// output element with JSON pointer "/number"
|
||||
std::cout << j["/number"_json_pointer] << '\n';
|
||||
// output element with JSON pointer "/string"
|
||||
std::cout << j["/string"_json_pointer] << '\n';
|
||||
// output element with JSON pointer "/array"
|
||||
std::cout << j["/array"_json_pointer] << '\n';
|
||||
// output element with JSON pointer "/array/1"
|
||||
std::cout << j["/array/1"_json_pointer] << '\n';
|
||||
}
|
1
doc/examples/operatorjson_pointer_const.link
Normal file
1
doc/examples/operatorjson_pointer_const.link
Normal file
|
@ -0,0 +1 @@
|
|||
<a target="_blank" href="http://melpon.org/wandbox/permlink/YmjwNAhsoeMXw5Ve"><b>online</b></a>
|
4
doc/examples/operatorjson_pointer_const.output
Normal file
4
doc/examples/operatorjson_pointer_const.output
Normal file
|
@ -0,0 +1,4 @@
|
|||
1
|
||||
"foo"
|
||||
[1,2]
|
||||
2
|
28
doc/examples/unflatten.cpp
Normal file
28
doc/examples/unflatten.cpp
Normal file
|
@ -0,0 +1,28 @@
|
|||
#include <json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
int main()
|
||||
{
|
||||
// create JSON value
|
||||
json j_flattened =
|
||||
{
|
||||
{"/answer/everything", 42},
|
||||
{"/happy", true},
|
||||
{"/list/0", 1},
|
||||
{"/list/1", 0},
|
||||
{"/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}
|
||||
};
|
||||
|
||||
// call unflatten()
|
||||
std::cout << std::setw(4) << j_flattened.unflatten() << '\n';
|
||||
}
|
1
doc/examples/unflatten.link
Normal file
1
doc/examples/unflatten.link
Normal file
|
@ -0,0 +1 @@
|
|||
<a target="_blank" href="http://melpon.org/wandbox/permlink/ITqCZsXmi0I7KGYy"><b>online</b></a>
|
22
doc/examples/unflatten.output
Normal file
22
doc/examples/unflatten.output
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"answer": {
|
||||
"everything": 42
|
||||
},
|
||||
"happy": true,
|
||||
"list": [
|
||||
1,
|
||||
0,
|
||||
2
|
||||
],
|
||||
"name": "Niels",
|
||||
"nothing": null,
|
||||
"object": {
|
||||
"": "empty string",
|
||||
"/": "slash",
|
||||
"currency": "USD",
|
||||
"value": 42.99,
|
||||
"~": "tilde",
|
||||
"~1": "tilde1"
|
||||
},
|
||||
"pi": 3.141
|
||||
}
|
386
src/json.hpp
386
src/json.hpp
|
@ -3598,23 +3598,86 @@ class basic_json
|
|||
/*!
|
||||
@brief access specified element via JSON Pointer
|
||||
|
||||
Returns a reference to the element at with specified JSON pointer @a ptr.
|
||||
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.
|
||||
|
||||
@param p JSON pointer to the desired element
|
||||
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 JSON value pointed to by @a ptr
|
||||
|
||||
@complexity Linear in the length of the JSON pointer.
|
||||
|
||||
@throw std::out_of_range 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(*this);
|
||||
return ptr.get_unchecked(this);
|
||||
}
|
||||
|
||||
/*!
|
||||
@copydoc basic_json::operator[](const json_pointer&)
|
||||
@brief access specified element via JSON Pointer
|
||||
|
||||
Uses a JSON pointer to retrieve a reference to the respective JSON value.
|
||||
No bound checking is performed. 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 a JSON pointer
|
||||
|
||||
@return reference to the JSON value pointed to by @a ptr
|
||||
|
||||
@complexity Linear in the length of the JSON pointer.
|
||||
|
||||
@throw std::out_of_range if the JSON pointer can not be resolved
|
||||
@throw std::out_of_range if the special value `-` is used for an array
|
||||
|
||||
@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(*this);
|
||||
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.
|
||||
|
||||
@param ptr JSON pointer to the desired element
|
||||
|
||||
@since version 2.0.0
|
||||
*/
|
||||
reference at(const json_pointer& ptr)
|
||||
{
|
||||
return ptr.get_checked(this);
|
||||
}
|
||||
|
||||
/*!
|
||||
@copydoc basic_json::at(const json_pointer&)
|
||||
*/
|
||||
const_reference at(const json_pointer& ptr) const
|
||||
{
|
||||
return ptr.get_checked(this);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -8841,45 +8904,28 @@ basic_json_parser_63:
|
|||
@brief JSON Pointer
|
||||
|
||||
@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:
|
||||
/// empty reference token
|
||||
json_pointer() = default;
|
||||
|
||||
/// nonempty reference token
|
||||
explicit json_pointer(const std::string& s)
|
||||
{
|
||||
split(s);
|
||||
}
|
||||
: reference_tokens(split(s))
|
||||
{}
|
||||
|
||||
private:
|
||||
reference get(reference j) const
|
||||
{
|
||||
pointer result = &j;
|
||||
|
||||
for (const auto& reference_token : reference_tokens)
|
||||
{
|
||||
switch (result->m_type)
|
||||
{
|
||||
case value_t::object:
|
||||
result = &result->at(reference_token);
|
||||
continue;
|
||||
|
||||
case value_t::array:
|
||||
result = &result->at(static_cast<size_t>(std::stoi(reference_token)));
|
||||
continue;
|
||||
|
||||
default:
|
||||
throw std::domain_error("unresolved reference token '" + reference_token + "'");
|
||||
}
|
||||
}
|
||||
|
||||
return *result;
|
||||
}
|
||||
|
||||
reference get2(reference j) const
|
||||
/*!
|
||||
@brief create and return a reference to the pointed to value
|
||||
*/
|
||||
reference get_and_create(reference j) const
|
||||
{
|
||||
pointer result = &j;
|
||||
|
||||
|
@ -8922,40 +8968,172 @@ basic_json_parser_63:
|
|||
return *result;
|
||||
}
|
||||
|
||||
const_reference get(const_reference j) const
|
||||
{
|
||||
const_pointer result = &j;
|
||||
/*!
|
||||
@brief return a reference to the pointed to value
|
||||
|
||||
@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 std::out_of_range if the JSON pointer can not be resolved
|
||||
*/
|
||||
reference get_unchecked(pointer ptr) const
|
||||
{
|
||||
for (const auto& reference_token : reference_tokens)
|
||||
{
|
||||
switch (result->m_type)
|
||||
switch (ptr->m_type)
|
||||
{
|
||||
case value_t::object:
|
||||
result = &result->at(reference_token);
|
||||
continue;
|
||||
{
|
||||
ptr = &ptr->operator[](reference_token);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::array:
|
||||
result = &result->at(static_cast<size_t>(std::stoi(reference_token)));
|
||||
continue;
|
||||
{
|
||||
if (reference_token == "-")
|
||||
{
|
||||
ptr = &ptr->operator[](ptr->m_value.array->size());
|
||||
}
|
||||
else
|
||||
{
|
||||
ptr = &ptr->operator[](static_cast<size_t>(std::stoi(reference_token)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw std::domain_error("unresolved reference token '" + reference_token + "'");
|
||||
{
|
||||
throw std::out_of_range("unresolved reference token '" + reference_token + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return *result;
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
/// the reference tokens
|
||||
std::vector<std::string> reference_tokens {};
|
||||
reference get_checked(pointer ptr) const
|
||||
{
|
||||
for (const auto& reference_token : reference_tokens)
|
||||
{
|
||||
switch (ptr->m_type)
|
||||
{
|
||||
case value_t::object:
|
||||
{
|
||||
ptr = &ptr->at(reference_token);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::array:
|
||||
{
|
||||
if (reference_token == "-")
|
||||
{
|
||||
throw std::out_of_range("cannot resolve reference token '-'");
|
||||
}
|
||||
else
|
||||
{
|
||||
ptr = &ptr->at(static_cast<size_t>(std::stoi(reference_token)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
throw std::out_of_range("unresolved reference token '" + reference_token + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief return a const reference to the pointed to value
|
||||
|
||||
@param[in] ptr a JSON value
|
||||
|
||||
@return const reference to the JSON value pointed to by the JSON
|
||||
pointer
|
||||
*/
|
||||
const_reference get_unchecked(const_pointer ptr) const
|
||||
{
|
||||
for (const auto& reference_token : reference_tokens)
|
||||
{
|
||||
switch (ptr->m_type)
|
||||
{
|
||||
case value_t::object:
|
||||
{
|
||||
ptr = &ptr->operator[](reference_token);
|
||||
continue;
|
||||
}
|
||||
|
||||
case value_t::array:
|
||||
{
|
||||
if (reference_token == "-")
|
||||
{
|
||||
throw std::out_of_range("array index '-' (" +
|
||||
std::to_string(ptr->m_value.array->size()) +
|
||||
") is out of range");
|
||||
}
|
||||
ptr = &ptr->operator[](static_cast<size_t>(std::stoi(reference_token)));
|
||||
continue;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
throw std::out_of_range("unresolved reference token '" + reference_token + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
const_reference get_checked(const_pointer ptr) const
|
||||
{
|
||||
for (const auto& reference_token : reference_tokens)
|
||||
{
|
||||
switch (ptr->m_type)
|
||||
{
|
||||
case value_t::object:
|
||||
{
|
||||
ptr = &ptr->at(reference_token);
|
||||
continue;
|
||||
}
|
||||
|
||||
case value_t::array:
|
||||
{
|
||||
if (reference_token == "-")
|
||||
{
|
||||
throw std::out_of_range("array index '-' (" +
|
||||
std::to_string(ptr->m_value.array->size()) +
|
||||
") is out of range");
|
||||
}
|
||||
ptr = &ptr->at(static_cast<size_t>(std::stoi(reference_token)));
|
||||
continue;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
throw std::out_of_range("unresolved reference token '" + reference_token + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
/// split the string input to reference tokens
|
||||
void split(std::string reference_string)
|
||||
std::vector<std::string> split(std::string reference_string)
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
|
||||
// special case: empty reference string -> no reference tokens
|
||||
if (reference_string.empty())
|
||||
{
|
||||
return;
|
||||
return result;
|
||||
}
|
||||
|
||||
// check if nonempty reference string begins with slash
|
||||
|
@ -9006,10 +9184,13 @@ basic_json_parser_63:
|
|||
replace_substring(reference_token, "~0", "~");
|
||||
|
||||
// finally, store the reference token
|
||||
reference_tokens.push_back(reference_token);
|
||||
result.push_back(reference_token);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
/*!
|
||||
@brief replace all occurrences of a substring by another string
|
||||
|
||||
|
@ -9042,6 +9223,8 @@ basic_json_parser_63:
|
|||
@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,
|
||||
|
@ -9051,27 +9234,43 @@ basic_json_parser_63:
|
|||
{
|
||||
case value_t::array:
|
||||
{
|
||||
// iterate array and use index as reference string
|
||||
for (size_t i = 0; i < value.m_value.array->size(); ++i)
|
||||
if (value.m_value.array->empty())
|
||||
{
|
||||
flatten(reference_string + "/" + std::to_string(i),
|
||||
value.m_value.array->operator[](i), result);
|
||||
// flatten empty array as null
|
||||
result[reference_string] = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
// iterate array and use index as reference string
|
||||
for (size_t i = 0; i < value.m_value.array->size(); ++i)
|
||||
{
|
||||
flatten(reference_string + "/" + std::to_string(i),
|
||||
value.m_value.array->operator[](i), result);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::object:
|
||||
{
|
||||
// iterate object and use keys as reference string
|
||||
for (const auto& element : *value.m_value.object)
|
||||
if (value.m_value.object->empty())
|
||||
{
|
||||
// escape "~"" to "~0" and "/" to "~1"
|
||||
std::string key(element.first);
|
||||
replace_substring(key, "~", "~0");
|
||||
replace_substring(key, "/", "~1");
|
||||
// flatten empty object as null
|
||||
result[reference_string] = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
// iterate object and use keys as reference string
|
||||
for (const auto& element : *value.m_value.object)
|
||||
{
|
||||
// escape "~"" to "~0" and "/" to "~1"
|
||||
std::string key(element.first);
|
||||
replace_substring(key, "~", "~0");
|
||||
replace_substring(key, "/", "~1");
|
||||
|
||||
flatten(reference_string + "/" + key,
|
||||
element.second, result);
|
||||
flatten(reference_string + "/" + key,
|
||||
element.second, result);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -9088,13 +9287,13 @@ basic_json_parser_63:
|
|||
/*!
|
||||
@param[in] value flattened JSON
|
||||
|
||||
@return deflattened JSON
|
||||
@return unflattened JSON
|
||||
*/
|
||||
static basic_json deflatten(const basic_json& value)
|
||||
static basic_json unflatten(const basic_json& value)
|
||||
{
|
||||
if (not value.is_object())
|
||||
{
|
||||
throw std::domain_error("only objects can be deflattened");
|
||||
throw std::domain_error("only objects can be unflattened");
|
||||
}
|
||||
|
||||
basic_json result;
|
||||
|
@ -9108,15 +9307,44 @@ basic_json_parser_63:
|
|||
}
|
||||
|
||||
// assign value to reference pointed to by JSON pointer
|
||||
json_pointer(element.first).get2(result) = element.second;
|
||||
json_pointer(element.first).get_and_create(result) = element.second;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
/// the reference tokens
|
||||
const std::vector<std::string> reference_tokens {};
|
||||
};
|
||||
|
||||
////////////////////////////
|
||||
// JSON Pointer functions //
|
||||
////////////////////////////
|
||||
|
||||
/// @name JSON Pointer functions
|
||||
/// @{
|
||||
|
||||
/*!
|
||||
@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 primitve values
|
||||
|
||||
@note Empty objects and arrays are flattened to `null`.
|
||||
|
||||
@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
|
||||
{
|
||||
|
@ -9126,12 +9354,38 @@ basic_json_parser_63:
|
|||
}
|
||||
|
||||
/*!
|
||||
@brief unflatten a previously flattened JSON value
|
||||
|
||||
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()`.
|
||||
|
||||
@complexity Linear in the size the JSON value.
|
||||
|
||||
@liveexample{The following code shows how a flattened JSON object is
|
||||
unflattened into the original nested JSON object.,unflatten}
|
||||
|
||||
@sa @ref flatten() for the reverse function
|
||||
|
||||
@since version 2.0.0
|
||||
*/
|
||||
basic_json deflatten() const
|
||||
basic_json unflatten() const
|
||||
{
|
||||
return json_pointer::deflatten(*this);
|
||||
return json_pointer::unflatten(*this);
|
||||
}
|
||||
|
||||
/// @}
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -3598,23 +3598,86 @@ class basic_json
|
|||
/*!
|
||||
@brief access specified element via JSON Pointer
|
||||
|
||||
Returns a reference to the element at with specified JSON pointer @a ptr.
|
||||
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.
|
||||
|
||||
@param p JSON pointer to the desired element
|
||||
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 JSON value pointed to by @a ptr
|
||||
|
||||
@complexity Linear in the length of the JSON pointer.
|
||||
|
||||
@throw std::out_of_range 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(*this);
|
||||
return ptr.get_unchecked(this);
|
||||
}
|
||||
|
||||
/*!
|
||||
@copydoc basic_json::operator[](const json_pointer&)
|
||||
@brief access specified element via JSON Pointer
|
||||
|
||||
Uses a JSON pointer to retrieve a reference to the respective JSON value.
|
||||
No bound checking is performed. 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 a JSON pointer
|
||||
|
||||
@return reference to the JSON value pointed to by @a ptr
|
||||
|
||||
@complexity Linear in the length of the JSON pointer.
|
||||
|
||||
@throw std::out_of_range if the JSON pointer can not be resolved
|
||||
@throw std::out_of_range if the special value `-` is used for an array
|
||||
|
||||
@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(*this);
|
||||
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.
|
||||
|
||||
@param ptr JSON pointer to the desired element
|
||||
|
||||
@since version 2.0.0
|
||||
*/
|
||||
reference at(const json_pointer& ptr)
|
||||
{
|
||||
return ptr.get_checked(this);
|
||||
}
|
||||
|
||||
/*!
|
||||
@copydoc basic_json::at(const json_pointer&)
|
||||
*/
|
||||
const_reference at(const json_pointer& ptr) const
|
||||
{
|
||||
return ptr.get_checked(this);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -8151,45 +8214,28 @@ class basic_json
|
|||
@brief JSON Pointer
|
||||
|
||||
@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:
|
||||
/// empty reference token
|
||||
json_pointer() = default;
|
||||
|
||||
/// nonempty reference token
|
||||
explicit json_pointer(const std::string& s)
|
||||
{
|
||||
split(s);
|
||||
}
|
||||
: reference_tokens(split(s))
|
||||
{}
|
||||
|
||||
private:
|
||||
reference get(reference j) const
|
||||
{
|
||||
pointer result = &j;
|
||||
|
||||
for (const auto& reference_token : reference_tokens)
|
||||
{
|
||||
switch (result->m_type)
|
||||
{
|
||||
case value_t::object:
|
||||
result = &result->at(reference_token);
|
||||
continue;
|
||||
|
||||
case value_t::array:
|
||||
result = &result->at(static_cast<size_t>(std::stoi(reference_token)));
|
||||
continue;
|
||||
|
||||
default:
|
||||
throw std::domain_error("unresolved reference token '" + reference_token + "'");
|
||||
}
|
||||
}
|
||||
|
||||
return *result;
|
||||
}
|
||||
|
||||
reference get2(reference j) const
|
||||
/*!
|
||||
@brief create and return a reference to the pointed to value
|
||||
*/
|
||||
reference get_and_create(reference j) const
|
||||
{
|
||||
pointer result = &j;
|
||||
|
||||
|
@ -8232,40 +8278,172 @@ class basic_json
|
|||
return *result;
|
||||
}
|
||||
|
||||
const_reference get(const_reference j) const
|
||||
{
|
||||
const_pointer result = &j;
|
||||
/*!
|
||||
@brief return a reference to the pointed to value
|
||||
|
||||
@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 std::out_of_range if the JSON pointer can not be resolved
|
||||
*/
|
||||
reference get_unchecked(pointer ptr) const
|
||||
{
|
||||
for (const auto& reference_token : reference_tokens)
|
||||
{
|
||||
switch (result->m_type)
|
||||
switch (ptr->m_type)
|
||||
{
|
||||
case value_t::object:
|
||||
result = &result->at(reference_token);
|
||||
continue;
|
||||
{
|
||||
ptr = &ptr->operator[](reference_token);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::array:
|
||||
result = &result->at(static_cast<size_t>(std::stoi(reference_token)));
|
||||
continue;
|
||||
{
|
||||
if (reference_token == "-")
|
||||
{
|
||||
ptr = &ptr->operator[](ptr->m_value.array->size());
|
||||
}
|
||||
else
|
||||
{
|
||||
ptr = &ptr->operator[](static_cast<size_t>(std::stoi(reference_token)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw std::domain_error("unresolved reference token '" + reference_token + "'");
|
||||
{
|
||||
throw std::out_of_range("unresolved reference token '" + reference_token + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return *result;
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
/// the reference tokens
|
||||
std::vector<std::string> reference_tokens {};
|
||||
reference get_checked(pointer ptr) const
|
||||
{
|
||||
for (const auto& reference_token : reference_tokens)
|
||||
{
|
||||
switch (ptr->m_type)
|
||||
{
|
||||
case value_t::object:
|
||||
{
|
||||
ptr = &ptr->at(reference_token);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::array:
|
||||
{
|
||||
if (reference_token == "-")
|
||||
{
|
||||
throw std::out_of_range("cannot resolve reference token '-'");
|
||||
}
|
||||
else
|
||||
{
|
||||
ptr = &ptr->at(static_cast<size_t>(std::stoi(reference_token)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
throw std::out_of_range("unresolved reference token '" + reference_token + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief return a const reference to the pointed to value
|
||||
|
||||
@param[in] ptr a JSON value
|
||||
|
||||
@return const reference to the JSON value pointed to by the JSON
|
||||
pointer
|
||||
*/
|
||||
const_reference get_unchecked(const_pointer ptr) const
|
||||
{
|
||||
for (const auto& reference_token : reference_tokens)
|
||||
{
|
||||
switch (ptr->m_type)
|
||||
{
|
||||
case value_t::object:
|
||||
{
|
||||
ptr = &ptr->operator[](reference_token);
|
||||
continue;
|
||||
}
|
||||
|
||||
case value_t::array:
|
||||
{
|
||||
if (reference_token == "-")
|
||||
{
|
||||
throw std::out_of_range("array index '-' (" +
|
||||
std::to_string(ptr->m_value.array->size()) +
|
||||
") is out of range");
|
||||
}
|
||||
ptr = &ptr->operator[](static_cast<size_t>(std::stoi(reference_token)));
|
||||
continue;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
throw std::out_of_range("unresolved reference token '" + reference_token + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
const_reference get_checked(const_pointer ptr) const
|
||||
{
|
||||
for (const auto& reference_token : reference_tokens)
|
||||
{
|
||||
switch (ptr->m_type)
|
||||
{
|
||||
case value_t::object:
|
||||
{
|
||||
ptr = &ptr->at(reference_token);
|
||||
continue;
|
||||
}
|
||||
|
||||
case value_t::array:
|
||||
{
|
||||
if (reference_token == "-")
|
||||
{
|
||||
throw std::out_of_range("array index '-' (" +
|
||||
std::to_string(ptr->m_value.array->size()) +
|
||||
") is out of range");
|
||||
}
|
||||
ptr = &ptr->at(static_cast<size_t>(std::stoi(reference_token)));
|
||||
continue;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
throw std::out_of_range("unresolved reference token '" + reference_token + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
/// split the string input to reference tokens
|
||||
void split(std::string reference_string)
|
||||
std::vector<std::string> split(std::string reference_string)
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
|
||||
// special case: empty reference string -> no reference tokens
|
||||
if (reference_string.empty())
|
||||
{
|
||||
return;
|
||||
return result;
|
||||
}
|
||||
|
||||
// check if nonempty reference string begins with slash
|
||||
|
@ -8316,10 +8494,13 @@ class basic_json
|
|||
replace_substring(reference_token, "~0", "~");
|
||||
|
||||
// finally, store the reference token
|
||||
reference_tokens.push_back(reference_token);
|
||||
result.push_back(reference_token);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
/*!
|
||||
@brief replace all occurrences of a substring by another string
|
||||
|
||||
|
@ -8352,6 +8533,8 @@ class basic_json
|
|||
@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,
|
||||
|
@ -8361,27 +8544,43 @@ class basic_json
|
|||
{
|
||||
case value_t::array:
|
||||
{
|
||||
// iterate array and use index as reference string
|
||||
for (size_t i = 0; i < value.m_value.array->size(); ++i)
|
||||
if (value.m_value.array->empty())
|
||||
{
|
||||
flatten(reference_string + "/" + std::to_string(i),
|
||||
value.m_value.array->operator[](i), result);
|
||||
// flatten empty array as null
|
||||
result[reference_string] = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
// iterate array and use index as reference string
|
||||
for (size_t i = 0; i < value.m_value.array->size(); ++i)
|
||||
{
|
||||
flatten(reference_string + "/" + std::to_string(i),
|
||||
value.m_value.array->operator[](i), result);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::object:
|
||||
{
|
||||
// iterate object and use keys as reference string
|
||||
for (const auto& element : *value.m_value.object)
|
||||
if (value.m_value.object->empty())
|
||||
{
|
||||
// escape "~"" to "~0" and "/" to "~1"
|
||||
std::string key(element.first);
|
||||
replace_substring(key, "~", "~0");
|
||||
replace_substring(key, "/", "~1");
|
||||
// flatten empty object as null
|
||||
result[reference_string] = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
// iterate object and use keys as reference string
|
||||
for (const auto& element : *value.m_value.object)
|
||||
{
|
||||
// escape "~"" to "~0" and "/" to "~1"
|
||||
std::string key(element.first);
|
||||
replace_substring(key, "~", "~0");
|
||||
replace_substring(key, "/", "~1");
|
||||
|
||||
flatten(reference_string + "/" + key,
|
||||
element.second, result);
|
||||
flatten(reference_string + "/" + key,
|
||||
element.second, result);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -8398,13 +8597,13 @@ class basic_json
|
|||
/*!
|
||||
@param[in] value flattened JSON
|
||||
|
||||
@return deflattened JSON
|
||||
@return unflattened JSON
|
||||
*/
|
||||
static basic_json deflatten(const basic_json& value)
|
||||
static basic_json unflatten(const basic_json& value)
|
||||
{
|
||||
if (not value.is_object())
|
||||
{
|
||||
throw std::domain_error("only objects can be deflattened");
|
||||
throw std::domain_error("only objects can be unflattened");
|
||||
}
|
||||
|
||||
basic_json result;
|
||||
|
@ -8418,15 +8617,44 @@ class basic_json
|
|||
}
|
||||
|
||||
// assign value to reference pointed to by JSON pointer
|
||||
json_pointer(element.first).get2(result) = element.second;
|
||||
json_pointer(element.first).get_and_create(result) = element.second;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
/// the reference tokens
|
||||
const std::vector<std::string> reference_tokens {};
|
||||
};
|
||||
|
||||
////////////////////////////
|
||||
// JSON Pointer functions //
|
||||
////////////////////////////
|
||||
|
||||
/// @name JSON Pointer functions
|
||||
/// @{
|
||||
|
||||
/*!
|
||||
@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 primitve values
|
||||
|
||||
@note Empty objects and arrays are flattened to `null`.
|
||||
|
||||
@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
|
||||
{
|
||||
|
@ -8436,12 +8664,38 @@ class basic_json
|
|||
}
|
||||
|
||||
/*!
|
||||
@brief unflatten a previously flattened JSON value
|
||||
|
||||
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()`.
|
||||
|
||||
@complexity Linear in the size the JSON value.
|
||||
|
||||
@liveexample{The following code shows how a flattened JSON object is
|
||||
unflattened into the original nested JSON object.,unflatten}
|
||||
|
||||
@sa @ref flatten() for the reverse function
|
||||
|
||||
@since version 2.0.0
|
||||
*/
|
||||
basic_json deflatten() const
|
||||
basic_json unflatten() const
|
||||
{
|
||||
return json_pointer::deflatten(*this);
|
||||
return json_pointer::unflatten(*this);
|
||||
}
|
||||
|
||||
/// @}
|
||||
};
|
||||
|
||||
|
||||
|
|
222
test/unit.cpp
222
test/unit.cpp
|
@ -12054,119 +12054,195 @@ TEST_CASE("Unicode", "[hide]")
|
|||
|
||||
TEST_CASE("JSON pointers")
|
||||
{
|
||||
SECTION("errors")
|
||||
{
|
||||
CHECK_THROWS_AS(json::json_pointer("foo"), std::domain_error);
|
||||
CHECK_THROWS_WITH(json::json_pointer("foo"), "JSON pointer must be empty or begin with '/'");
|
||||
|
||||
CHECK_THROWS_AS(json::json_pointer("/~~"), std::domain_error);
|
||||
CHECK_THROWS_WITH(json::json_pointer("/~~"), "escape error: '~' must be followed with '0' or '1'");
|
||||
|
||||
CHECK_THROWS_AS(json::json_pointer("/~"), std::domain_error);
|
||||
CHECK_THROWS_WITH(json::json_pointer("/~"), "escape error: '~' must be followed with '0' or '1'");
|
||||
}
|
||||
|
||||
SECTION("examples from RFC 6901")
|
||||
{
|
||||
json j = R"(
|
||||
{
|
||||
"foo": ["bar", "baz"],
|
||||
"": 0,
|
||||
"a/b": 1,
|
||||
"c%d": 2,
|
||||
"e^f": 3,
|
||||
"g|h": 4,
|
||||
"i\\j": 5,
|
||||
"k\"l": 6,
|
||||
" ": 7,
|
||||
"m~n": 8
|
||||
}
|
||||
)"_json;
|
||||
|
||||
const json j_const = j;
|
||||
|
||||
SECTION("nonconst access")
|
||||
{
|
||||
json j = R"(
|
||||
{
|
||||
"foo": ["bar", "baz"],
|
||||
"": 0,
|
||||
"a/b": 1,
|
||||
"c%d": 2,
|
||||
"e^f": 3,
|
||||
"g|h": 4,
|
||||
"i\\j": 5,
|
||||
"k\"l": 6,
|
||||
" ": 7,
|
||||
"m~n": 8
|
||||
}
|
||||
)"_json;
|
||||
|
||||
// the whole document
|
||||
CHECK(json::json_pointer().get(j) == j);
|
||||
CHECK(json::json_pointer("").get(j) == j);
|
||||
CHECK(j[json::json_pointer()] == j);
|
||||
CHECK(j[json::json_pointer("")] == j);
|
||||
|
||||
// array access
|
||||
CHECK(json::json_pointer("/foo").get(j) == j["foo"]);
|
||||
CHECK(json::json_pointer("/foo/0").get(j) == j["foo"][0]);
|
||||
CHECK(json::json_pointer("/foo/1").get(j) == j["foo"][1]);
|
||||
CHECK(j[json::json_pointer("/foo")] == j["foo"]);
|
||||
CHECK(j[json::json_pointer("/foo/0")] == j["foo"][0]);
|
||||
CHECK(j[json::json_pointer("/foo/1")] == j["foo"][1]);
|
||||
CHECK(j["/foo/1"_json_pointer] == j["foo"][1]);
|
||||
|
||||
// empty string access
|
||||
CHECK(json::json_pointer("/").get(j) == j[""]);
|
||||
CHECK(j[json::json_pointer("/")] == j[""]);
|
||||
|
||||
// other cases
|
||||
CHECK(json::json_pointer("/ ").get(j) == j[" "]);
|
||||
CHECK(json::json_pointer("/c%d").get(j) == j["c%d"]);
|
||||
CHECK(json::json_pointer("/e^f").get(j) == j["e^f"]);
|
||||
CHECK(json::json_pointer("/g|h").get(j) == j["g|h"]);
|
||||
CHECK(json::json_pointer("/i\\j").get(j) == j["i\\j"]);
|
||||
CHECK(json::json_pointer("/k\"l").get(j) == j["k\"l"]);
|
||||
CHECK(j[json::json_pointer("/ ")] == j[" "]);
|
||||
CHECK(j[json::json_pointer("/c%d")] == j["c%d"]);
|
||||
CHECK(j[json::json_pointer("/e^f")] == j["e^f"]);
|
||||
CHECK(j[json::json_pointer("/g|h")] == j["g|h"]);
|
||||
CHECK(j[json::json_pointer("/i\\j")] == j["i\\j"]);
|
||||
CHECK(j[json::json_pointer("/k\"l")] == j["k\"l"]);
|
||||
|
||||
// escaped access
|
||||
CHECK(json::json_pointer("/a~1b").get(j) == j["a/b"]);
|
||||
CHECK(json::json_pointer("/m~0n").get(j) == j["m~n"]);
|
||||
CHECK(j[json::json_pointer("/a~1b")] == j["a/b"]);
|
||||
CHECK(j[json::json_pointer("/m~0n")] == j["m~n"]);
|
||||
|
||||
// unescaped access
|
||||
CHECK_THROWS_AS(json::json_pointer("/a/b").get(j), std::out_of_range);
|
||||
CHECK_THROWS_WITH(json::json_pointer("/a/b").get(j), "key 'a' not found");
|
||||
CHECK_THROWS_AS(j[json::json_pointer("/a/b")], std::out_of_range);
|
||||
CHECK_THROWS_WITH(j[json::json_pointer("/a/b")], "unresolved reference token 'b'");
|
||||
// "/a/b" works for JSON {"a": {"b": 42}}
|
||||
CHECK(json::json_pointer("/a/b").get({{"a", {{"b", 42}}}}) == json(42));
|
||||
CHECK(json({{"a", {{"b", 42}}}})[json::json_pointer("/a/b")] == json(42));
|
||||
}
|
||||
|
||||
SECTION("const access")
|
||||
{
|
||||
const json j = R"(
|
||||
{
|
||||
"foo": ["bar", "baz"],
|
||||
"": 0,
|
||||
"a/b": 1,
|
||||
"c%d": 2,
|
||||
"e^f": 3,
|
||||
"g|h": 4,
|
||||
"i\\j": 5,
|
||||
"k\"l": 6,
|
||||
" ": 7,
|
||||
"m~n": 8
|
||||
}
|
||||
)"_json;
|
||||
|
||||
// the whole document
|
||||
CHECK(json::json_pointer().get(j_const) == j_const);
|
||||
CHECK(json::json_pointer("").get(j_const) == j_const);
|
||||
CHECK(j[json::json_pointer()] == j);
|
||||
CHECK(j[json::json_pointer("")] == j);
|
||||
|
||||
// array access
|
||||
CHECK(json::json_pointer("/foo").get(j_const) == j_const["foo"]);
|
||||
CHECK(json::json_pointer("/foo/0").get(j_const) == j_const["foo"][0]);
|
||||
CHECK(json::json_pointer("/foo/1").get(j_const) == j_const["foo"][1]);
|
||||
CHECK(j[json::json_pointer("/foo")] == j["foo"]);
|
||||
CHECK(j[json::json_pointer("/foo/0")] == j["foo"][0]);
|
||||
CHECK(j[json::json_pointer("/foo/1")] == j["foo"][1]);
|
||||
CHECK(j["/foo/1"_json_pointer] == j["foo"][1]);
|
||||
|
||||
// empty string access
|
||||
CHECK(json::json_pointer("/").get(j_const) == j_const[""]);
|
||||
CHECK(j[json::json_pointer("/")] == j[""]);
|
||||
|
||||
// other cases
|
||||
CHECK(json::json_pointer("/ ").get(j_const) == j_const[" "]);
|
||||
CHECK(json::json_pointer("/c%d").get(j_const) == j_const["c%d"]);
|
||||
CHECK(json::json_pointer("/e^f").get(j_const) == j_const["e^f"]);
|
||||
CHECK(json::json_pointer("/g|h").get(j_const) == j_const["g|h"]);
|
||||
CHECK(json::json_pointer("/i\\j").get(j_const) == j_const["i\\j"]);
|
||||
CHECK(json::json_pointer("/k\"l").get(j_const) == j_const["k\"l"]);
|
||||
CHECK(j[json::json_pointer("/ ")] == j[" "]);
|
||||
CHECK(j[json::json_pointer("/c%d")] == j["c%d"]);
|
||||
CHECK(j[json::json_pointer("/e^f")] == j["e^f"]);
|
||||
CHECK(j[json::json_pointer("/g|h")] == j["g|h"]);
|
||||
CHECK(j[json::json_pointer("/i\\j")] == j["i\\j"]);
|
||||
CHECK(j[json::json_pointer("/k\"l")] == j["k\"l"]);
|
||||
|
||||
// escaped access
|
||||
CHECK(json::json_pointer("/a~1b").get(j_const) == j_const["a/b"]);
|
||||
CHECK(json::json_pointer("/m~0n").get(j_const) == j_const["m~n"]);
|
||||
CHECK(j[json::json_pointer("/a~1b")] == j["a/b"]);
|
||||
CHECK(j[json::json_pointer("/m~0n")] == j["m~n"]);
|
||||
|
||||
// unescaped access
|
||||
CHECK_THROWS_AS(json::json_pointer("/a/b").get(j), std::out_of_range);
|
||||
CHECK_THROWS_WITH(json::json_pointer("/a/b").get(j), "key 'a' not found");
|
||||
// "/a/b" works for JSON {"a": {"b": 42}}
|
||||
CHECK(json::json_pointer("/a/b").get({{"a", {{"b", 42}}}}) == json(42));
|
||||
CHECK_THROWS_AS(j.at(json::json_pointer("/a/b")), std::out_of_range);
|
||||
CHECK_THROWS_WITH(j.at(json::json_pointer("/a/b")), "key 'a' not found");
|
||||
}
|
||||
|
||||
SECTION("user-defined string literal")
|
||||
{
|
||||
json j = R"(
|
||||
{
|
||||
"foo": ["bar", "baz"],
|
||||
"": 0,
|
||||
"a/b": 1,
|
||||
"c%d": 2,
|
||||
"e^f": 3,
|
||||
"g|h": 4,
|
||||
"i\\j": 5,
|
||||
"k\"l": 6,
|
||||
" ": 7,
|
||||
"m~n": 8
|
||||
}
|
||||
)"_json;
|
||||
|
||||
// the whole document
|
||||
CHECK(""_json_pointer.get(j) == j);
|
||||
CHECK(j[""_json_pointer] == j);
|
||||
|
||||
// array access
|
||||
CHECK("/foo"_json_pointer.get(j) == j["foo"]);
|
||||
CHECK("/foo/0"_json_pointer.get(j) == j["foo"][0]);
|
||||
CHECK("/foo/1"_json_pointer.get(j) == j["foo"][1]);
|
||||
CHECK(j["/foo"_json_pointer] == j["foo"]);
|
||||
CHECK(j["/foo/0"_json_pointer] == j["foo"][0]);
|
||||
CHECK(j["/foo/1"_json_pointer] == j["foo"][1]);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("errors")
|
||||
SECTION("array access")
|
||||
{
|
||||
SECTION("nonconst access")
|
||||
{
|
||||
CHECK_THROWS_AS(json::json_pointer("foo"), std::domain_error);
|
||||
CHECK_THROWS_WITH(json::json_pointer("foo"), "JSON pointer must be empty or begin with '/'");
|
||||
json j = {1, 2, 3};
|
||||
|
||||
CHECK_THROWS_AS(json::json_pointer("/~~"), std::domain_error);
|
||||
CHECK_THROWS_WITH(json::json_pointer("/~~"), "escape error: '~' must be followed with '0' or '1'");
|
||||
// check reading access
|
||||
CHECK(j["/0"_json_pointer] == j[0]);
|
||||
CHECK(j["/1"_json_pointer] == j[1]);
|
||||
CHECK(j["/2"_json_pointer] == j[2]);
|
||||
|
||||
CHECK_THROWS_AS(json::json_pointer("/~"), std::domain_error);
|
||||
CHECK_THROWS_WITH(json::json_pointer("/~"), "escape error: '~' must be followed with '0' or '1'");
|
||||
// assign to existing index
|
||||
j["/1"_json_pointer] = 13;
|
||||
CHECK(j[1] == json(13));
|
||||
|
||||
// assign to nonexisting index
|
||||
j["/3"_json_pointer] = 33;
|
||||
CHECK(j[3] == json(33));
|
||||
|
||||
// assign to nonexisting index (with gap)
|
||||
j["/5"_json_pointer] = 55;
|
||||
CHECK(j == json({1, 13, 3, 33, nullptr, 55}));
|
||||
|
||||
// assign to "-"
|
||||
j["/-"_json_pointer] = 99;
|
||||
CHECK(j == json({1, 13, 3, 33, nullptr, 55, 99}));
|
||||
}
|
||||
|
||||
SECTION("const access")
|
||||
{
|
||||
const json j = {1, 2, 3};
|
||||
|
||||
// check reading access
|
||||
CHECK(j["/0"_json_pointer] == j[0]);
|
||||
CHECK(j["/1"_json_pointer] == j[1]);
|
||||
CHECK(j["/2"_json_pointer] == j[2]);
|
||||
|
||||
// assign to nonexisting index
|
||||
CHECK_THROWS_AS(j.at("/3"_json_pointer), std::out_of_range);
|
||||
CHECK_THROWS_WITH(j.at("/3"_json_pointer), "array index 3 is out of range");
|
||||
|
||||
// assign to nonexisting index (with gap)
|
||||
CHECK_THROWS_AS(j.at("/5"_json_pointer), std::out_of_range);
|
||||
CHECK_THROWS_WITH(j.at("/5"_json_pointer), "array index 5 is out of range");
|
||||
|
||||
// assign to "-"
|
||||
CHECK_THROWS_AS(j["/-"_json_pointer], std::out_of_range);
|
||||
CHECK_THROWS_WITH(j["/-"_json_pointer], "array index '-' (3) is out of range");
|
||||
CHECK_THROWS_AS(j.at("/-"_json_pointer), std::out_of_range);
|
||||
CHECK_THROWS_WITH(j.at("/-"_json_pointer), "array index '-' (3) is out of range");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SECTION("flatten")
|
||||
|
@ -12216,21 +12292,27 @@ TEST_CASE("JSON pointers")
|
|||
// check if flattened result is as expected
|
||||
CHECK(j.flatten() == j_flatten);
|
||||
|
||||
// check if deflattened result is as expected
|
||||
CHECK(j_flatten.deflatten() == j);
|
||||
// check if unflattened result is as expected
|
||||
CHECK(j_flatten.unflatten() == j);
|
||||
|
||||
// explicit roundtrip check
|
||||
CHECK(j.flatten().deflatten() == j);
|
||||
CHECK(j.flatten().unflatten() == j);
|
||||
|
||||
// roundtrip for primitive values
|
||||
json j_null;
|
||||
CHECK(j_null.flatten().deflatten() == j_null);
|
||||
CHECK(j_null.flatten().unflatten() == j_null);
|
||||
json j_number = 42;
|
||||
CHECK(j_number.flatten().deflatten() == j_number);
|
||||
CHECK(j_number.flatten().unflatten() == j_number);
|
||||
json j_boolean = false;
|
||||
CHECK(j_boolean.flatten().deflatten() == j_boolean);
|
||||
CHECK(j_boolean.flatten().unflatten() == j_boolean);
|
||||
json j_string = "foo";
|
||||
CHECK(j_string.flatten().deflatten() == j_string);
|
||||
CHECK(j_string.flatten().unflatten() == j_string);
|
||||
|
||||
// roundtrip for empty structured values (will be unflattened to null)
|
||||
json j_array(json::value_t::array);
|
||||
CHECK(j_array.flatten().unflatten() == json());
|
||||
json j_object(json::value_t::object);
|
||||
CHECK(j_object.flatten().unflatten() == json());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue