cleanup and documentation

This commit is contained in:
Niels 2016-04-17 17:39:35 +02:00
parent f834965b44
commit 40e899a819
15 changed files with 978 additions and 202 deletions

34
doc/examples/flatten.cpp Normal file
View 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';
}

View file

@ -0,0 +1 @@
<a target="_blank" href="http://melpon.org/wandbox/permlink/kODXfzcksgstdBRD"><b>online</b></a>

View 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
}

View 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';
}

View file

@ -0,0 +1 @@
<a target="_blank" href="http://melpon.org/wandbox/permlink/6oeNnra3wjPijLSr"><b>online</b></a>

View 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]

View 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';
}

View file

@ -0,0 +1 @@
<a target="_blank" href="http://melpon.org/wandbox/permlink/YmjwNAhsoeMXw5Ve"><b>online</b></a>

View file

@ -0,0 +1,4 @@
1
"foo"
[1,2]
2

View 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';
}

View file

@ -0,0 +1 @@
<a target="_blank" href="http://melpon.org/wandbox/permlink/ITqCZsXmi0I7KGYy"><b>online</b></a>

View 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
}

View file

@ -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,
@ -9050,6 +9233,13 @@ basic_json_parser_63:
switch (value.m_type)
{
case value_t::array:
{
if (value.m_value.array->empty())
{
// flatten empty array as null
result[reference_string] = nullptr;
}
else
{
// iterate array and use index as reference string
for (size_t i = 0; i < value.m_value.array->size(); ++i)
@ -9057,10 +9247,18 @@ basic_json_parser_63:
flatten(reference_string + "/" + std::to_string(i),
value.m_value.array->operator[](i), result);
}
}
break;
}
case value_t::object:
{
if (value.m_value.object->empty())
{
// flatten empty object as null
result[reference_string] = nullptr;
}
else
{
// iterate object and use keys as reference string
for (const auto& element : *value.m_value.object)
@ -9073,6 +9271,7 @@ basic_json_parser_63:
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);
}
/// @}
};

View file

@ -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,
@ -8360,6 +8543,13 @@ class basic_json
switch (value.m_type)
{
case value_t::array:
{
if (value.m_value.array->empty())
{
// flatten empty array as null
result[reference_string] = nullptr;
}
else
{
// iterate array and use index as reference string
for (size_t i = 0; i < value.m_value.array->size(); ++i)
@ -8367,10 +8557,18 @@ class basic_json
flatten(reference_string + "/" + std::to_string(i),
value.m_value.array->operator[](i), result);
}
}
break;
}
case value_t::object:
{
if (value.m_value.object->empty())
{
// flatten empty object as null
result[reference_string] = nullptr;
}
else
{
// iterate object and use keys as reference string
for (const auto& element : *value.m_value.object)
@ -8383,6 +8581,7 @@ class basic_json
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);
}
/// @}
};

View file

@ -12054,7 +12054,21 @@ 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")
{
SECTION("nonconst access")
{
json j = R"(
{
@ -12071,102 +12085,164 @@ TEST_CASE("JSON pointers")
}
)"_json;
const json j_const = j;
SECTION("nonconst access")
{
// 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")
{
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 '/'");
SECTION("nonconst access")
{
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());
}
}