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
|
||||||
|
}
|
364
src/json.hpp
364
src/json.hpp
|
@ -3598,23 +3598,86 @@ class basic_json
|
||||||
/*!
|
/*!
|
||||||
@brief access specified element via JSON Pointer
|
@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
|
@since version 2.0.0
|
||||||
*/
|
*/
|
||||||
reference operator[](const json_pointer& ptr)
|
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
|
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
|
@brief JSON Pointer
|
||||||
|
|
||||||
@sa [RFC 6901](https://tools.ietf.org/html/rfc6901)
|
@sa [RFC 6901](https://tools.ietf.org/html/rfc6901)
|
||||||
|
|
||||||
|
@since version 2.0.0
|
||||||
*/
|
*/
|
||||||
class json_pointer
|
class json_pointer
|
||||||
{
|
{
|
||||||
|
/// allow basic_json to access private members
|
||||||
|
friend class basic_json;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// empty reference token
|
/// empty reference token
|
||||||
json_pointer() = default;
|
json_pointer() = default;
|
||||||
|
|
||||||
/// nonempty reference token
|
/// nonempty reference token
|
||||||
explicit json_pointer(const std::string& s)
|
explicit json_pointer(const std::string& s)
|
||||||
{
|
: reference_tokens(split(s))
|
||||||
split(s);
|
{}
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
reference get(reference j) const
|
/*!
|
||||||
{
|
@brief create and return a reference to the pointed to value
|
||||||
pointer result = &j;
|
*/
|
||||||
|
reference get_and_create(reference j) const
|
||||||
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
|
|
||||||
{
|
{
|
||||||
pointer result = &j;
|
pointer result = &j;
|
||||||
|
|
||||||
|
@ -8922,40 +8968,172 @@ basic_json_parser_63:
|
||||||
return *result;
|
return *result;
|
||||||
}
|
}
|
||||||
|
|
||||||
const_reference get(const_reference j) const
|
/*!
|
||||||
{
|
@brief return a reference to the pointed to value
|
||||||
const_pointer result = &j;
|
|
||||||
|
|
||||||
|
@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)
|
for (const auto& reference_token : reference_tokens)
|
||||||
{
|
{
|
||||||
switch (result->m_type)
|
switch (ptr->m_type)
|
||||||
{
|
{
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
result = &result->at(reference_token);
|
{
|
||||||
continue;
|
ptr = &ptr->operator[](reference_token);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case value_t::array:
|
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:
|
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
|
reference get_checked(pointer ptr) const
|
||||||
std::vector<std::string> reference_tokens {};
|
{
|
||||||
|
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
|
/// 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
|
// special case: empty reference string -> no reference tokens
|
||||||
if (reference_string.empty())
|
if (reference_string.empty())
|
||||||
{
|
{
|
||||||
return;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if nonempty reference string begins with slash
|
// check if nonempty reference string begins with slash
|
||||||
|
@ -9006,10 +9184,13 @@ basic_json_parser_63:
|
||||||
replace_substring(reference_token, "~0", "~");
|
replace_substring(reference_token, "~0", "~");
|
||||||
|
|
||||||
// finally, store the reference token
|
// 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
|
@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] reference_string the reference string to the current value
|
||||||
@param[in] value the value to consider
|
@param[in] value the value to consider
|
||||||
@param[in,out] result the result object to insert values to
|
@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,
|
static void flatten(const std::string reference_string,
|
||||||
const basic_json& value,
|
const basic_json& value,
|
||||||
|
@ -9050,6 +9233,13 @@ basic_json_parser_63:
|
||||||
switch (value.m_type)
|
switch (value.m_type)
|
||||||
{
|
{
|
||||||
case value_t::array:
|
case value_t::array:
|
||||||
|
{
|
||||||
|
if (value.m_value.array->empty())
|
||||||
|
{
|
||||||
|
// flatten empty array as null
|
||||||
|
result[reference_string] = nullptr;
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
// iterate array and use index as reference string
|
// iterate array and use index as reference string
|
||||||
for (size_t i = 0; i < value.m_value.array->size(); ++i)
|
for (size_t i = 0; i < value.m_value.array->size(); ++i)
|
||||||
|
@ -9057,10 +9247,18 @@ basic_json_parser_63:
|
||||||
flatten(reference_string + "/" + std::to_string(i),
|
flatten(reference_string + "/" + std::to_string(i),
|
||||||
value.m_value.array->operator[](i), result);
|
value.m_value.array->operator[](i), result);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
|
{
|
||||||
|
if (value.m_value.object->empty())
|
||||||
|
{
|
||||||
|
// flatten empty object as null
|
||||||
|
result[reference_string] = nullptr;
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
// iterate object and use keys as reference string
|
// iterate object and use keys as reference string
|
||||||
for (const auto& element : *value.m_value.object)
|
for (const auto& element : *value.m_value.object)
|
||||||
|
@ -9073,6 +9271,7 @@ basic_json_parser_63:
|
||||||
flatten(reference_string + "/" + key,
|
flatten(reference_string + "/" + key,
|
||||||
element.second, result);
|
element.second, result);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9088,13 +9287,13 @@ basic_json_parser_63:
|
||||||
/*!
|
/*!
|
||||||
@param[in] value flattened 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())
|
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;
|
basic_json result;
|
||||||
|
@ -9108,15 +9307,44 @@ basic_json_parser_63:
|
||||||
}
|
}
|
||||||
|
|
||||||
// assign value to reference pointed to by JSON pointer
|
// 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;
|
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
|
@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
|
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
|
@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
|
@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
|
@since version 2.0.0
|
||||||
*/
|
*/
|
||||||
reference operator[](const json_pointer& ptr)
|
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
|
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
|
@brief JSON Pointer
|
||||||
|
|
||||||
@sa [RFC 6901](https://tools.ietf.org/html/rfc6901)
|
@sa [RFC 6901](https://tools.ietf.org/html/rfc6901)
|
||||||
|
|
||||||
|
@since version 2.0.0
|
||||||
*/
|
*/
|
||||||
class json_pointer
|
class json_pointer
|
||||||
{
|
{
|
||||||
|
/// allow basic_json to access private members
|
||||||
|
friend class basic_json;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// empty reference token
|
/// empty reference token
|
||||||
json_pointer() = default;
|
json_pointer() = default;
|
||||||
|
|
||||||
/// nonempty reference token
|
/// nonempty reference token
|
||||||
explicit json_pointer(const std::string& s)
|
explicit json_pointer(const std::string& s)
|
||||||
{
|
: reference_tokens(split(s))
|
||||||
split(s);
|
{}
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
reference get(reference j) const
|
/*!
|
||||||
{
|
@brief create and return a reference to the pointed to value
|
||||||
pointer result = &j;
|
*/
|
||||||
|
reference get_and_create(reference j) const
|
||||||
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
|
|
||||||
{
|
{
|
||||||
pointer result = &j;
|
pointer result = &j;
|
||||||
|
|
||||||
|
@ -8232,40 +8278,172 @@ class basic_json
|
||||||
return *result;
|
return *result;
|
||||||
}
|
}
|
||||||
|
|
||||||
const_reference get(const_reference j) const
|
/*!
|
||||||
{
|
@brief return a reference to the pointed to value
|
||||||
const_pointer result = &j;
|
|
||||||
|
|
||||||
|
@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)
|
for (const auto& reference_token : reference_tokens)
|
||||||
{
|
{
|
||||||
switch (result->m_type)
|
switch (ptr->m_type)
|
||||||
{
|
{
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
result = &result->at(reference_token);
|
{
|
||||||
continue;
|
ptr = &ptr->operator[](reference_token);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case value_t::array:
|
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:
|
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
|
reference get_checked(pointer ptr) const
|
||||||
std::vector<std::string> reference_tokens {};
|
{
|
||||||
|
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
|
/// 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
|
// special case: empty reference string -> no reference tokens
|
||||||
if (reference_string.empty())
|
if (reference_string.empty())
|
||||||
{
|
{
|
||||||
return;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if nonempty reference string begins with slash
|
// check if nonempty reference string begins with slash
|
||||||
|
@ -8316,10 +8494,13 @@ class basic_json
|
||||||
replace_substring(reference_token, "~0", "~");
|
replace_substring(reference_token, "~0", "~");
|
||||||
|
|
||||||
// finally, store the reference token
|
// 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
|
@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] reference_string the reference string to the current value
|
||||||
@param[in] value the value to consider
|
@param[in] value the value to consider
|
||||||
@param[in,out] result the result object to insert values to
|
@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,
|
static void flatten(const std::string reference_string,
|
||||||
const basic_json& value,
|
const basic_json& value,
|
||||||
|
@ -8360,6 +8543,13 @@ class basic_json
|
||||||
switch (value.m_type)
|
switch (value.m_type)
|
||||||
{
|
{
|
||||||
case value_t::array:
|
case value_t::array:
|
||||||
|
{
|
||||||
|
if (value.m_value.array->empty())
|
||||||
|
{
|
||||||
|
// flatten empty array as null
|
||||||
|
result[reference_string] = nullptr;
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
// iterate array and use index as reference string
|
// iterate array and use index as reference string
|
||||||
for (size_t i = 0; i < value.m_value.array->size(); ++i)
|
for (size_t i = 0; i < value.m_value.array->size(); ++i)
|
||||||
|
@ -8367,10 +8557,18 @@ class basic_json
|
||||||
flatten(reference_string + "/" + std::to_string(i),
|
flatten(reference_string + "/" + std::to_string(i),
|
||||||
value.m_value.array->operator[](i), result);
|
value.m_value.array->operator[](i), result);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
|
{
|
||||||
|
if (value.m_value.object->empty())
|
||||||
|
{
|
||||||
|
// flatten empty object as null
|
||||||
|
result[reference_string] = nullptr;
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
// iterate object and use keys as reference string
|
// iterate object and use keys as reference string
|
||||||
for (const auto& element : *value.m_value.object)
|
for (const auto& element : *value.m_value.object)
|
||||||
|
@ -8383,6 +8581,7 @@ class basic_json
|
||||||
flatten(reference_string + "/" + key,
|
flatten(reference_string + "/" + key,
|
||||||
element.second, result);
|
element.second, result);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8398,13 +8597,13 @@ class basic_json
|
||||||
/*!
|
/*!
|
||||||
@param[in] value flattened 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())
|
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;
|
basic_json result;
|
||||||
|
@ -8418,15 +8617,44 @@ class basic_json
|
||||||
}
|
}
|
||||||
|
|
||||||
// assign value to reference pointed to by JSON pointer
|
// 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;
|
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
|
@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
|
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
|
@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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
196
test/unit.cpp
196
test/unit.cpp
|
@ -12054,7 +12054,21 @@ TEST_CASE("Unicode", "[hide]")
|
||||||
|
|
||||||
TEST_CASE("JSON pointers")
|
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")
|
SECTION("examples from RFC 6901")
|
||||||
|
{
|
||||||
|
SECTION("nonconst access")
|
||||||
{
|
{
|
||||||
json j = R"(
|
json j = R"(
|
||||||
{
|
{
|
||||||
|
@ -12071,102 +12085,164 @@ TEST_CASE("JSON pointers")
|
||||||
}
|
}
|
||||||
)"_json;
|
)"_json;
|
||||||
|
|
||||||
const json j_const = j;
|
|
||||||
|
|
||||||
SECTION("nonconst access")
|
|
||||||
{
|
|
||||||
// the whole document
|
// 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);
|
||||||
CHECK(j[json::json_pointer("")] == j);
|
CHECK(j[json::json_pointer("")] == j);
|
||||||
|
|
||||||
// array access
|
// 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")] == j["foo"]);
|
||||||
CHECK(j[json::json_pointer("/foo/0")] == j["foo"][0]);
|
CHECK(j[json::json_pointer("/foo/0")] == j["foo"][0]);
|
||||||
CHECK(j[json::json_pointer("/foo/1")] == j["foo"][1]);
|
CHECK(j[json::json_pointer("/foo/1")] == j["foo"][1]);
|
||||||
CHECK(j["/foo/1"_json_pointer] == j["foo"][1]);
|
CHECK(j["/foo/1"_json_pointer] == j["foo"][1]);
|
||||||
|
|
||||||
// empty string access
|
// empty string access
|
||||||
CHECK(json::json_pointer("/").get(j) == j[""]);
|
CHECK(j[json::json_pointer("/")] == j[""]);
|
||||||
|
|
||||||
// other cases
|
// other cases
|
||||||
CHECK(json::json_pointer("/ ").get(j) == j[" "]);
|
CHECK(j[json::json_pointer("/ ")] == j[" "]);
|
||||||
CHECK(json::json_pointer("/c%d").get(j) == j["c%d"]);
|
CHECK(j[json::json_pointer("/c%d")] == j["c%d"]);
|
||||||
CHECK(json::json_pointer("/e^f").get(j) == j["e^f"]);
|
CHECK(j[json::json_pointer("/e^f")] == j["e^f"]);
|
||||||
CHECK(json::json_pointer("/g|h").get(j) == j["g|h"]);
|
CHECK(j[json::json_pointer("/g|h")] == j["g|h"]);
|
||||||
CHECK(json::json_pointer("/i\\j").get(j) == j["i\\j"]);
|
CHECK(j[json::json_pointer("/i\\j")] == j["i\\j"]);
|
||||||
CHECK(json::json_pointer("/k\"l").get(j) == j["k\"l"]);
|
CHECK(j[json::json_pointer("/k\"l")] == j["k\"l"]);
|
||||||
|
|
||||||
// escaped access
|
// escaped access
|
||||||
CHECK(json::json_pointer("/a~1b").get(j) == j["a/b"]);
|
CHECK(j[json::json_pointer("/a~1b")] == j["a/b"]);
|
||||||
CHECK(json::json_pointer("/m~0n").get(j) == j["m~n"]);
|
CHECK(j[json::json_pointer("/m~0n")] == j["m~n"]);
|
||||||
|
|
||||||
// unescaped access
|
// unescaped access
|
||||||
CHECK_THROWS_AS(json::json_pointer("/a/b").get(j), std::out_of_range);
|
CHECK_THROWS_AS(j[json::json_pointer("/a/b")], std::out_of_range);
|
||||||
CHECK_THROWS_WITH(json::json_pointer("/a/b").get(j), "key 'a' not found");
|
CHECK_THROWS_WITH(j[json::json_pointer("/a/b")], "unresolved reference token 'b'");
|
||||||
// "/a/b" works for JSON {"a": {"b": 42}}
|
// "/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")
|
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
|
// the whole document
|
||||||
CHECK(json::json_pointer().get(j_const) == j_const);
|
CHECK(j[json::json_pointer()] == j);
|
||||||
CHECK(json::json_pointer("").get(j_const) == j_const);
|
CHECK(j[json::json_pointer("")] == j);
|
||||||
|
|
||||||
// array access
|
// array access
|
||||||
CHECK(json::json_pointer("/foo").get(j_const) == j_const["foo"]);
|
CHECK(j[json::json_pointer("/foo")] == j["foo"]);
|
||||||
CHECK(json::json_pointer("/foo/0").get(j_const) == j_const["foo"][0]);
|
CHECK(j[json::json_pointer("/foo/0")] == j["foo"][0]);
|
||||||
CHECK(json::json_pointer("/foo/1").get(j_const) == j_const["foo"][1]);
|
CHECK(j[json::json_pointer("/foo/1")] == j["foo"][1]);
|
||||||
|
CHECK(j["/foo/1"_json_pointer] == j["foo"][1]);
|
||||||
|
|
||||||
// empty string access
|
// empty string access
|
||||||
CHECK(json::json_pointer("/").get(j_const) == j_const[""]);
|
CHECK(j[json::json_pointer("/")] == j[""]);
|
||||||
|
|
||||||
// other cases
|
// other cases
|
||||||
CHECK(json::json_pointer("/ ").get(j_const) == j_const[" "]);
|
CHECK(j[json::json_pointer("/ ")] == j[" "]);
|
||||||
CHECK(json::json_pointer("/c%d").get(j_const) == j_const["c%d"]);
|
CHECK(j[json::json_pointer("/c%d")] == j["c%d"]);
|
||||||
CHECK(json::json_pointer("/e^f").get(j_const) == j_const["e^f"]);
|
CHECK(j[json::json_pointer("/e^f")] == j["e^f"]);
|
||||||
CHECK(json::json_pointer("/g|h").get(j_const) == j_const["g|h"]);
|
CHECK(j[json::json_pointer("/g|h")] == j["g|h"]);
|
||||||
CHECK(json::json_pointer("/i\\j").get(j_const) == j_const["i\\j"]);
|
CHECK(j[json::json_pointer("/i\\j")] == j["i\\j"]);
|
||||||
CHECK(json::json_pointer("/k\"l").get(j_const) == j_const["k\"l"]);
|
CHECK(j[json::json_pointer("/k\"l")] == j["k\"l"]);
|
||||||
|
|
||||||
// escaped access
|
// escaped access
|
||||||
CHECK(json::json_pointer("/a~1b").get(j_const) == j_const["a/b"]);
|
CHECK(j[json::json_pointer("/a~1b")] == j["a/b"]);
|
||||||
CHECK(json::json_pointer("/m~0n").get(j_const) == j_const["m~n"]);
|
CHECK(j[json::json_pointer("/m~0n")] == j["m~n"]);
|
||||||
|
|
||||||
// unescaped access
|
// unescaped access
|
||||||
CHECK_THROWS_AS(json::json_pointer("/a/b").get(j), std::out_of_range);
|
CHECK_THROWS_AS(j.at(json::json_pointer("/a/b")), std::out_of_range);
|
||||||
CHECK_THROWS_WITH(json::json_pointer("/a/b").get(j), "key 'a' not found");
|
CHECK_THROWS_WITH(j.at(json::json_pointer("/a/b")), "key 'a' not found");
|
||||||
// "/a/b" works for JSON {"a": {"b": 42}}
|
|
||||||
CHECK(json::json_pointer("/a/b").get({{"a", {{"b", 42}}}}) == json(42));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("user-defined string literal")
|
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
|
// the whole document
|
||||||
CHECK(""_json_pointer.get(j) == j);
|
CHECK(j[""_json_pointer] == j);
|
||||||
|
|
||||||
// array access
|
// array access
|
||||||
CHECK("/foo"_json_pointer.get(j) == j["foo"]);
|
CHECK(j["/foo"_json_pointer] == j["foo"]);
|
||||||
CHECK("/foo/0"_json_pointer.get(j) == j["foo"][0]);
|
CHECK(j["/foo/0"_json_pointer] == j["foo"][0]);
|
||||||
CHECK("/foo/1"_json_pointer.get(j) == j["foo"][1]);
|
CHECK(j["/foo/1"_json_pointer] == j["foo"][1]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("errors")
|
SECTION("array access")
|
||||||
{
|
{
|
||||||
CHECK_THROWS_AS(json::json_pointer("foo"), std::domain_error);
|
SECTION("nonconst access")
|
||||||
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 reading access
|
||||||
CHECK_THROWS_WITH(json::json_pointer("/~~"), "escape error: '~' must be followed with '0' or '1'");
|
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);
|
// assign to existing index
|
||||||
CHECK_THROWS_WITH(json::json_pointer("/~"), "escape error: '~' must be followed with '0' or '1'");
|
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")
|
SECTION("flatten")
|
||||||
|
@ -12216,21 +12292,27 @@ TEST_CASE("JSON pointers")
|
||||||
// check if flattened result is as expected
|
// check if flattened result is as expected
|
||||||
CHECK(j.flatten() == j_flatten);
|
CHECK(j.flatten() == j_flatten);
|
||||||
|
|
||||||
// check if deflattened result is as expected
|
// check if unflattened result is as expected
|
||||||
CHECK(j_flatten.deflatten() == j);
|
CHECK(j_flatten.unflatten() == j);
|
||||||
|
|
||||||
// explicit roundtrip check
|
// explicit roundtrip check
|
||||||
CHECK(j.flatten().deflatten() == j);
|
CHECK(j.flatten().unflatten() == j);
|
||||||
|
|
||||||
// roundtrip for primitive values
|
// roundtrip for primitive values
|
||||||
json j_null;
|
json j_null;
|
||||||
CHECK(j_null.flatten().deflatten() == j_null);
|
CHECK(j_null.flatten().unflatten() == j_null);
|
||||||
json j_number = 42;
|
json j_number = 42;
|
||||||
CHECK(j_number.flatten().deflatten() == j_number);
|
CHECK(j_number.flatten().unflatten() == j_number);
|
||||||
json j_boolean = false;
|
json j_boolean = false;
|
||||||
CHECK(j_boolean.flatten().deflatten() == j_boolean);
|
CHECK(j_boolean.flatten().unflatten() == j_boolean);
|
||||||
json j_string = "foo";
|
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