Merge branch 'feature/json-pointer' into develop

This commit is contained in:
Niels 2016-04-17 23:23:34 +02:00
commit 9f8da4c650
25 changed files with 2044 additions and 18 deletions

View file

@ -428,7 +428,7 @@ $ make
$ ./json_unit "*"
===============================================================================
All tests passed (3344299 assertions in 29 test cases)
All tests passed (3344416 assertions in 30 test cases)
```
For more information, have a look at the file [.travis.yml](https://github.com/nlohmann/json/blob/master/.travis.yml).

View file

@ -0,0 +1,35 @@
#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.at("/number"_json_pointer) << '\n';
// output element with JSON pointer "/string"
std::cout << j.at("/string"_json_pointer) << '\n';
// output element with JSON pointer "/array"
std::cout << j.at("/array"_json_pointer) << '\n';
// output element with JSON pointer "/array/1"
std::cout << j.at("/array/1"_json_pointer) << '\n';
// writing access
// change the string
j.at("/string"_json_pointer) = "bar";
// output the changed string
std::cout << j["string"] << '\n';
// change an array element
j.at("/array/1"_json_pointer) = 21;
// output the changed array
std::cout << j["array"] << '\n';
}

View file

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

View file

@ -0,0 +1,6 @@
1
"foo"
[1,2]
2
"bar"
[1,21]

View file

@ -0,0 +1,23 @@
#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.at("/number"_json_pointer) << '\n';
// output element with JSON pointer "/string"
std::cout << j.at("/string"_json_pointer) << '\n';
// output element with JSON pointer "/array"
std::cout << j.at("/array"_json_pointer) << '\n';
// output element with JSON pointer "/array/1"
std::cout << j.at("/array/1"_json_pointer) << '\n';
}

View file

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

View file

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

37
doc/examples/flatten.cpp Normal file
View file

@ -0,0 +1,37 @@
#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';
// flatten for a primitive value
std::cout << j["pi"].flatten() << '\n';
}

View file

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

View file

@ -0,0 +1,17 @@
{
"/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
}
{"":3.141}

View file

@ -0,0 +1,46 @@
#include <json.hpp>
using json = nlohmann::json;
int main()
{
// correct JSON pointers
json::json_pointer p1;
json::json_pointer p2("");
json::json_pointer p3("/");
json::json_pointer p4("//");
json::json_pointer p5("/foo/bar");
json::json_pointer p6("/foo/bar/-");
json::json_pointer p7("/foo/~0");
json::json_pointer p8("/foo/~1");
// error: JSON pointer does not begin with a slash
try
{
json::json_pointer p9("foo");
}
catch (std::domain_error& e)
{
std::cout << "domain_error: " << e.what() << '\n';
}
// error: JSON pointer uses escape symbol ~ not followed by 0 or 1
try
{
json::json_pointer p10("/foo/~");
}
catch (std::domain_error& e)
{
std::cout << "domain_error: " << e.what() << '\n';
}
// error: JSON pointer uses escape symbol ~ not followed by 0 or 1
try
{
json::json_pointer p11("/foo/~3");
}
catch (std::domain_error& e)
{
std::cout << "domain_error: " << e.what() << '\n';
}
}

View file

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

View file

@ -0,0 +1,3 @@
domain_error: JSON pointer must be empty or begin with '/'
domain_error: escape error: '~' must be followed with '0' or '1'
domain_error: escape error: '~' must be followed with '0' or '1'

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

@ -198,6 +198,9 @@ class basic_json
AllocatorType>;
public:
// forward declarations
template<typename Base> class json_reverse_iterator;
class json_pointer;
/////////////////////
// container types //
@ -227,9 +230,6 @@ class basic_json
/// the type of an element const pointer
using const_pointer = typename std::allocator_traits<allocator_type>::const_pointer;
// forward declaration
template<typename Base> class json_reverse_iterator;
/// an iterator for a basic_json container
class iterator;
/// a const iterator for a basic_json container
@ -3273,8 +3273,8 @@ class basic_json
@return reference to the element at index @a idx
@throw std::domain_error if JSON is not an array or null; example: `"cannot
use operator[] with string"`
@throw std::domain_error if JSON is not an array or null; example:
`"cannot use operator[] with string"`
@complexity Constant if @a idx is in the range of the array. Otherwise
linear in `idx - size()`.
@ -3595,6 +3595,121 @@ class basic_json
}
}
/*!
@brief access specified element via JSON Pointer
Uses a JSON pointer to retrieve a reference to the respective JSON value.
No bound checking is performed. Similar to
@ref operator[](const typename object_t::key_type&), `null` values
are created in arrays and objects if necessary.
In particular:
- If the JSON pointer points to an object key that does not exist, it
is created an filled with a `null` value before a reference to it
is returned.
- If the JSON pointer points to an array index that does not exist, it
is created an filled with a `null` value before a reference to it
is returned. All indices between the current maximum and the given
index are also filled with `null`.
- The special value `-` is treated as a synonym for the index past the
end.
@param[in] ptr a JSON pointer
@return reference to the element pointed to by @a ptr
@complexity Constant.
@throw std::out_of_range if the JSON pointer can not be resolved
@throw std::domain_error if an array index begins with '0'
@throw std::invalid_argument if an array index was not a number
@liveexample{The behavior is shown in the example.,operatorjson_pointer}
@since version 2.0.0
*/
reference operator[](const json_pointer& ptr)
{
return ptr.get_unchecked(this);
}
/*!
@brief access specified element via JSON Pointer
Uses a JSON pointer to retrieve a reference to the respective JSON value.
No bound checking is performed. The function does not change the JSON
value; no `null` values are created. In particular, the the special value
`-` yields an exception.
@param[in] ptr JSON pointer to the desired element
@return const reference to the element pointed to by @a ptr
@complexity Constant.
@throw std::out_of_range if the JSON pointer can not be resolved
@throw std::domain_error if an array index begins with '0'
@throw std::invalid_argument if an array index was not a number
@liveexample{The behavior is shown in the example.,operatorjson_pointer_const}
@since version 2.0.0
*/
const_reference operator[](const json_pointer& ptr) const
{
return ptr.get_unchecked(this);
}
/*!
@brief access specified element via JSON Pointer
Returns a reference to the element at with specified JSON pointer @a ptr,
with bounds checking.
@param[in] ptr JSON pointer to the desired element
@return reference to the element pointed to by @a ptr
@complexity Constant.
@throw std::out_of_range if the JSON pointer can not be resolved
@throw std::domain_error if an array index begins with '0'
@throw std::invalid_argument if an array index was not a number
@liveexample{The behavior is shown in the example.,at_json_pointer}
@since version 2.0.0
*/
reference at(const json_pointer& ptr)
{
return ptr.get_checked(this);
}
/*!
@brief access specified element via JSON Pointer
Returns a const reference to the element at with specified JSON pointer
@a ptr, with bounds checking.
@param[in] ptr JSON pointer to the desired element
@return reference to the element pointed to by @a ptr
@complexity Constant.
@throw std::out_of_range if the JSON pointer can not be resolved
@throw std::domain_error if an array index begins with '0'
@throw std::invalid_argument if an array index was not a number
@liveexample{The behavior is shown in the example.,at_json_pointer_const}
@since version 2.0.0
*/
const_reference at(const json_pointer& ptr) const
{
return ptr.get_checked(this);
}
/*!
@brief access specified object element with default value
@ -8813,6 +8928,570 @@ basic_json_parser_63:
/// the lexer
lexer m_lexer;
};
public:
/*!
@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:
/*!
@brief create JSON pointer
Create a JSON pointer according to the syntax described in
[Section 3 of RFC6901](https://tools.ietf.org/html/rfc6901#section-3).
@param[in] s string representing the JSON pointer; if omitted, the
empty string is assumed which references the whole JSON
value
@throw std::domain_error if reference token is nonempty and does not
begin with a slash (`/`); example: `"JSON pointer must be empty or
begin with /"`
@throw std::domain_error if a tilde (`~`) is not followed by `0`
(representing `~`) or `1` (representing `/`); example: `"escape error:
~ must be followed with 0 or 1"`
@liveexample{The example shows the construction several valid JSON
pointers as well as the exceptional behavior.,json_pointer}
@since version 2.0.0
*/
explicit json_pointer(const std::string& s = "")
: reference_tokens(split(s))
{}
private:
/*!
@brief create and return a reference to the pointed to value
*/
reference get_and_create(reference j) const
{
pointer result = &j;
// in case no reference tokens exist, return a reference to the
// JSON value j which will be overwritten by a primitive value
for (const auto& reference_token : reference_tokens)
{
switch (result->m_type)
{
case value_t::null:
{
if (reference_token == "0")
{
// start a new array if reference token is 0
result = &result->operator[](0);
}
else
{
// start a new object otherwise
result = &result->operator[](reference_token);
}
break;
}
case value_t::object:
{
// create an entry in the object
result = &result->operator[](reference_token);
break;
}
case value_t::array:
{
// create an entry in the array
result = &result->operator[](static_cast<size_t>(std::stoi(reference_token)));
break;
}
/*
The following code is only reached if there exists a
reference token _and_ the current value is primitive. In
this case, we have an error situation, because primitive
values may only occur as single value; that is, with an
empty list of reference tokens.
*/
default:
{
throw std::domain_error("invalid value to unflatten");
}
}
}
return *result;
}
/*!
@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
@throw std::domain_error if an array index begins with '0'
@throw std::invalid_argument if an array index was not a number
*/
reference get_unchecked(pointer ptr) const
{
for (const auto& reference_token : reference_tokens)
{
switch (ptr->m_type)
{
case value_t::object:
{
// use unchecked object access
ptr = &ptr->operator[](reference_token);
break;
}
case value_t::array:
{
// error condition (cf. RFC 6901, Sect. 4)
if (reference_token.size() > 1 and reference_token[0] == '0')
{
throw std::domain_error("array index must not begin with '0'");
}
if (reference_token == "-")
{
// explicityly treat "-" as index beyond the end
ptr = &ptr->operator[](ptr->m_value.array->size());
}
else
{
// convert array index to number; unchecked access
ptr = &ptr->operator[](static_cast<size_t>(std::stoi(reference_token)));
}
break;
}
default:
{
throw std::out_of_range("unresolved reference token '" + reference_token + "'");
}
}
}
return *ptr;
}
reference get_checked(pointer ptr) const
{
for (const auto& reference_token : reference_tokens)
{
switch (ptr->m_type)
{
case value_t::object:
{
// note: at performs range check
ptr = &ptr->at(reference_token);
break;
}
case value_t::array:
{
if (reference_token == "-")
{
// "-" always fails the range check
throw std::out_of_range("array index '-' (" +
std::to_string(ptr->m_value.array->size()) +
") is out of range");
}
// error condition (cf. RFC 6901, Sect. 4)
if (reference_token.size() > 1 and reference_token[0] == '0')
{
throw std::domain_error("array index must not begin with '0'");
}
// note: at performs range check
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:
{
// use unchecked object access
ptr = &ptr->operator[](reference_token);
break;
}
case value_t::array:
{
if (reference_token == "-")
{
// "-" cannot be used for const access
throw std::out_of_range("array index '-' (" +
std::to_string(ptr->m_value.array->size()) +
") is out of range");
}
// error condition (cf. RFC 6901, Sect. 4)
if (reference_token.size() > 1 and reference_token[0] == '0')
{
throw std::domain_error("array index must not begin with '0'");
}
// use unchecked array access
ptr = &ptr->operator[](static_cast<size_t>(std::stoi(reference_token)));
break;
}
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:
{
// note: at performs range check
ptr = &ptr->at(reference_token);
break;
}
case value_t::array:
{
if (reference_token == "-")
{
// "-" always fails the range check
throw std::out_of_range("array index '-' (" +
std::to_string(ptr->m_value.array->size()) +
") is out of range");
}
// error condition (cf. RFC 6901, Sect. 4)
if (reference_token.size() > 1 and reference_token[0] == '0')
{
throw std::domain_error("array index must not begin with '0'");
}
// note: at performs range check
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;
}
/// split the string input to reference tokens
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 result;
}
// check if nonempty reference string begins with slash
if (reference_string[0] != '/')
{
throw std::domain_error("JSON pointer must be empty or begin with '/'");
}
// extract the reference tokens:
// - slash: position of the last read slash (or end of string)
// - start: position after the previous slash
for (
// search for the first slash after the first character
size_t slash = reference_string.find_first_of("/", 1),
// set the beginning of the first reference token
start = 1;
// we can stop if start == string::npos+1 = 0
start != 0;
// set the beginning of the next reference token
// (will eventually be 0 if slash == std::string::npos)
start = slash + 1,
// find next slash
slash = reference_string.find_first_of("/", start))
{
// use the text between the beginning of the reference token
// (start) and the last slash (slash).
auto reference_token = reference_string.substr(start, slash - start);
// check reference tokens are properly escaped
for (size_t pos = reference_token.find_first_of("~");
pos != std::string::npos;
pos = reference_token.find_first_of("~", pos + 1))
{
assert(reference_token[pos] == '~');
// ~ must be followed by 0 or 1
if (pos == reference_token.size() - 1 or
(reference_token[pos + 1] != '0' and
reference_token[pos + 1] != '1'))
{
throw std::domain_error("escape error: '~' must be followed with '0' or '1'");
}
}
// first transform any occurrence of the sequence '~1' to '/'
replace_substring(reference_token, "~1", "/");
// then transform any occurrence of the sequence '~0' to '~'
replace_substring(reference_token, "~0", "~");
// finally, store the reference token
result.push_back(reference_token);
}
return result;
}
private:
/*!
@brief replace all occurrences of a substring by another string
@param[in,out] s the string to manipulate
@param[in] f the substring to replace with @a t
@param[out] t the string to replace @a f
@return The string @a s where all occurrences of @a f are replaced
with @a t.
@pre The search string @a f must not be empty.
@since version 2.0.0
*/
static void replace_substring(std::string& s,
const std::string& f,
const std::string& t)
{
assert(not f.empty());
for (
size_t pos = s.find(f); // find first occurrence of f
pos != std::string::npos; // make sure f was found
s.replace(pos, f.size(), t), // replace with t
pos = s.find(f, pos + t.size()) // find next occurrence of f
);
}
/*!
@param[in] reference_string the reference string to the current value
@param[in] value the value to consider
@param[in,out] result the result object to insert values to
@note Empty objects or arrays are flattened to `null`.
*/
static void flatten(const std::string reference_string,
const basic_json& value,
basic_json& result)
{
switch (value.m_type)
{
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)
{
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)
{
// 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);
}
}
break;
}
default:
{
// add primitive value with its reference string
result[reference_string] = value;
break;
}
}
}
/*!
@param[in] value flattened JSON
@return unflattened JSON
*/
static basic_json unflatten(const basic_json& value)
{
if (not value.is_object())
{
throw std::domain_error("only objects can be unflattened");
}
basic_json result;
// iterate the JSON object values
for (const auto& element : *value.m_value.object)
{
if (not element.second.is_primitive())
{
throw std::domain_error("values in object must be primitive");
}
// assign value to reference pointed to by JSON pointer;
// Note that if the JSON pointer is "" (i.e., points to the
// whole value), function get_and_create returns a reference
// to result itself. An assignment will then create a
// primitive value.
json_pointer(element.first).get_and_create(result) = element.second;
}
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` and will not be
reconstructed correctly by the @ref unflatten() function.
@complexity Linear in the size the JSON value.
@liveexample{The following code shows how a JSON object is flattened to an
object whose keys consist of JSON pointers.,flatten}
@sa @ref unflatten() for the reverse function
@since version 2.0.0
*/
basic_json flatten() const
{
basic_json result(value_t::object);
json_pointer::flatten("", *this, result);
return result;
}
/*!
@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 unflatten() const
{
return json_pointer::unflatten(*this);
}
/// @}
};
@ -8875,9 +9554,9 @@ struct hash<nlohmann::json>
/*!
@brief user-defined string literal for JSON values
This operator implements a user-defined string literal for JSON objects. It can
be used by adding \p "_json" to a string literal and returns a JSON object if
no parse error occurred.
This operator implements a user-defined string literal for JSON objects. It
can be used by adding \p "_json" to a string literal and returns a JSON object
if no parse error occurred.
@param[in] s a string representation of a JSON object
@return a JSON object
@ -8889,6 +9568,16 @@ inline nlohmann::json operator "" _json(const char* s, std::size_t)
return nlohmann::json::parse(reinterpret_cast<const nlohmann::json::string_t::value_type*>(s));
}
/*!
@brief user-defined string literal for JSON pointer
@since version 2.0.0
*/
inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t)
{
return nlohmann::json::json_pointer(s);
}
// restore GCC/clang diagnostic settings
#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__)
#pragma GCC diagnostic pop

View file

@ -198,6 +198,9 @@ class basic_json
AllocatorType>;
public:
// forward declarations
template<typename Base> class json_reverse_iterator;
class json_pointer;
/////////////////////
// container types //
@ -227,9 +230,6 @@ class basic_json
/// the type of an element const pointer
using const_pointer = typename std::allocator_traits<allocator_type>::const_pointer;
// forward declaration
template<typename Base> class json_reverse_iterator;
/// an iterator for a basic_json container
class iterator;
/// a const iterator for a basic_json container
@ -3273,8 +3273,8 @@ class basic_json
@return reference to the element at index @a idx
@throw std::domain_error if JSON is not an array or null; example: `"cannot
use operator[] with string"`
@throw std::domain_error if JSON is not an array or null; example:
`"cannot use operator[] with string"`
@complexity Constant if @a idx is in the range of the array. Otherwise
linear in `idx - size()`.
@ -3595,6 +3595,121 @@ class basic_json
}
}
/*!
@brief access specified element via JSON Pointer
Uses a JSON pointer to retrieve a reference to the respective JSON value.
No bound checking is performed. Similar to
@ref operator[](const typename object_t::key_type&), `null` values
are created in arrays and objects if necessary.
In particular:
- If the JSON pointer points to an object key that does not exist, it
is created an filled with a `null` value before a reference to it
is returned.
- If the JSON pointer points to an array index that does not exist, it
is created an filled with a `null` value before a reference to it
is returned. All indices between the current maximum and the given
index are also filled with `null`.
- The special value `-` is treated as a synonym for the index past the
end.
@param[in] ptr a JSON pointer
@return reference to the element pointed to by @a ptr
@complexity Constant.
@throw std::out_of_range if the JSON pointer can not be resolved
@throw std::domain_error if an array index begins with '0'
@throw std::invalid_argument if an array index was not a number
@liveexample{The behavior is shown in the example.,operatorjson_pointer}
@since version 2.0.0
*/
reference operator[](const json_pointer& ptr)
{
return ptr.get_unchecked(this);
}
/*!
@brief access specified element via JSON Pointer
Uses a JSON pointer to retrieve a reference to the respective JSON value.
No bound checking is performed. The function does not change the JSON
value; no `null` values are created. In particular, the the special value
`-` yields an exception.
@param[in] ptr JSON pointer to the desired element
@return const reference to the element pointed to by @a ptr
@complexity Constant.
@throw std::out_of_range if the JSON pointer can not be resolved
@throw std::domain_error if an array index begins with '0'
@throw std::invalid_argument if an array index was not a number
@liveexample{The behavior is shown in the example.,operatorjson_pointer_const}
@since version 2.0.0
*/
const_reference operator[](const json_pointer& ptr) const
{
return ptr.get_unchecked(this);
}
/*!
@brief access specified element via JSON Pointer
Returns a reference to the element at with specified JSON pointer @a ptr,
with bounds checking.
@param[in] ptr JSON pointer to the desired element
@return reference to the element pointed to by @a ptr
@complexity Constant.
@throw std::out_of_range if the JSON pointer can not be resolved
@throw std::domain_error if an array index begins with '0'
@throw std::invalid_argument if an array index was not a number
@liveexample{The behavior is shown in the example.,at_json_pointer}
@since version 2.0.0
*/
reference at(const json_pointer& ptr)
{
return ptr.get_checked(this);
}
/*!
@brief access specified element via JSON Pointer
Returns a const reference to the element at with specified JSON pointer
@a ptr, with bounds checking.
@param[in] ptr JSON pointer to the desired element
@return reference to the element pointed to by @a ptr
@complexity Constant.
@throw std::out_of_range if the JSON pointer can not be resolved
@throw std::domain_error if an array index begins with '0'
@throw std::invalid_argument if an array index was not a number
@liveexample{The behavior is shown in the example.,at_json_pointer_const}
@since version 2.0.0
*/
const_reference at(const json_pointer& ptr) const
{
return ptr.get_checked(this);
}
/*!
@brief access specified object element with default value
@ -8123,6 +8238,570 @@ class basic_json
/// the lexer
lexer m_lexer;
};
public:
/*!
@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:
/*!
@brief create JSON pointer
Create a JSON pointer according to the syntax described in
[Section 3 of RFC6901](https://tools.ietf.org/html/rfc6901#section-3).
@param[in] s string representing the JSON pointer; if omitted, the
empty string is assumed which references the whole JSON
value
@throw std::domain_error if reference token is nonempty and does not
begin with a slash (`/`); example: `"JSON pointer must be empty or
begin with /"`
@throw std::domain_error if a tilde (`~`) is not followed by `0`
(representing `~`) or `1` (representing `/`); example: `"escape error:
~ must be followed with 0 or 1"`
@liveexample{The example shows the construction several valid JSON
pointers as well as the exceptional behavior.,json_pointer}
@since version 2.0.0
*/
explicit json_pointer(const std::string& s = "")
: reference_tokens(split(s))
{}
private:
/*!
@brief create and return a reference to the pointed to value
*/
reference get_and_create(reference j) const
{
pointer result = &j;
// in case no reference tokens exist, return a reference to the
// JSON value j which will be overwritten by a primitive value
for (const auto& reference_token : reference_tokens)
{
switch (result->m_type)
{
case value_t::null:
{
if (reference_token == "0")
{
// start a new array if reference token is 0
result = &result->operator[](0);
}
else
{
// start a new object otherwise
result = &result->operator[](reference_token);
}
break;
}
case value_t::object:
{
// create an entry in the object
result = &result->operator[](reference_token);
break;
}
case value_t::array:
{
// create an entry in the array
result = &result->operator[](static_cast<size_t>(std::stoi(reference_token)));
break;
}
/*
The following code is only reached if there exists a
reference token _and_ the current value is primitive. In
this case, we have an error situation, because primitive
values may only occur as single value; that is, with an
empty list of reference tokens.
*/
default:
{
throw std::domain_error("invalid value to unflatten");
}
}
}
return *result;
}
/*!
@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
@throw std::domain_error if an array index begins with '0'
@throw std::invalid_argument if an array index was not a number
*/
reference get_unchecked(pointer ptr) const
{
for (const auto& reference_token : reference_tokens)
{
switch (ptr->m_type)
{
case value_t::object:
{
// use unchecked object access
ptr = &ptr->operator[](reference_token);
break;
}
case value_t::array:
{
// error condition (cf. RFC 6901, Sect. 4)
if (reference_token.size() > 1 and reference_token[0] == '0')
{
throw std::domain_error("array index must not begin with '0'");
}
if (reference_token == "-")
{
// explicityly treat "-" as index beyond the end
ptr = &ptr->operator[](ptr->m_value.array->size());
}
else
{
// convert array index to number; unchecked access
ptr = &ptr->operator[](static_cast<size_t>(std::stoi(reference_token)));
}
break;
}
default:
{
throw std::out_of_range("unresolved reference token '" + reference_token + "'");
}
}
}
return *ptr;
}
reference get_checked(pointer ptr) const
{
for (const auto& reference_token : reference_tokens)
{
switch (ptr->m_type)
{
case value_t::object:
{
// note: at performs range check
ptr = &ptr->at(reference_token);
break;
}
case value_t::array:
{
if (reference_token == "-")
{
// "-" always fails the range check
throw std::out_of_range("array index '-' (" +
std::to_string(ptr->m_value.array->size()) +
") is out of range");
}
// error condition (cf. RFC 6901, Sect. 4)
if (reference_token.size() > 1 and reference_token[0] == '0')
{
throw std::domain_error("array index must not begin with '0'");
}
// note: at performs range check
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:
{
// use unchecked object access
ptr = &ptr->operator[](reference_token);
break;
}
case value_t::array:
{
if (reference_token == "-")
{
// "-" cannot be used for const access
throw std::out_of_range("array index '-' (" +
std::to_string(ptr->m_value.array->size()) +
") is out of range");
}
// error condition (cf. RFC 6901, Sect. 4)
if (reference_token.size() > 1 and reference_token[0] == '0')
{
throw std::domain_error("array index must not begin with '0'");
}
// use unchecked array access
ptr = &ptr->operator[](static_cast<size_t>(std::stoi(reference_token)));
break;
}
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:
{
// note: at performs range check
ptr = &ptr->at(reference_token);
break;
}
case value_t::array:
{
if (reference_token == "-")
{
// "-" always fails the range check
throw std::out_of_range("array index '-' (" +
std::to_string(ptr->m_value.array->size()) +
") is out of range");
}
// error condition (cf. RFC 6901, Sect. 4)
if (reference_token.size() > 1 and reference_token[0] == '0')
{
throw std::domain_error("array index must not begin with '0'");
}
// note: at performs range check
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;
}
/// split the string input to reference tokens
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 result;
}
// check if nonempty reference string begins with slash
if (reference_string[0] != '/')
{
throw std::domain_error("JSON pointer must be empty or begin with '/'");
}
// extract the reference tokens:
// - slash: position of the last read slash (or end of string)
// - start: position after the previous slash
for (
// search for the first slash after the first character
size_t slash = reference_string.find_first_of("/", 1),
// set the beginning of the first reference token
start = 1;
// we can stop if start == string::npos+1 = 0
start != 0;
// set the beginning of the next reference token
// (will eventually be 0 if slash == std::string::npos)
start = slash + 1,
// find next slash
slash = reference_string.find_first_of("/", start))
{
// use the text between the beginning of the reference token
// (start) and the last slash (slash).
auto reference_token = reference_string.substr(start, slash - start);
// check reference tokens are properly escaped
for (size_t pos = reference_token.find_first_of("~");
pos != std::string::npos;
pos = reference_token.find_first_of("~", pos + 1))
{
assert(reference_token[pos] == '~');
// ~ must be followed by 0 or 1
if (pos == reference_token.size() - 1 or
(reference_token[pos + 1] != '0' and
reference_token[pos + 1] != '1'))
{
throw std::domain_error("escape error: '~' must be followed with '0' or '1'");
}
}
// first transform any occurrence of the sequence '~1' to '/'
replace_substring(reference_token, "~1", "/");
// then transform any occurrence of the sequence '~0' to '~'
replace_substring(reference_token, "~0", "~");
// finally, store the reference token
result.push_back(reference_token);
}
return result;
}
private:
/*!
@brief replace all occurrences of a substring by another string
@param[in,out] s the string to manipulate
@param[in] f the substring to replace with @a t
@param[out] t the string to replace @a f
@return The string @a s where all occurrences of @a f are replaced
with @a t.
@pre The search string @a f must not be empty.
@since version 2.0.0
*/
static void replace_substring(std::string& s,
const std::string& f,
const std::string& t)
{
assert(not f.empty());
for (
size_t pos = s.find(f); // find first occurrence of f
pos != std::string::npos; // make sure f was found
s.replace(pos, f.size(), t), // replace with t
pos = s.find(f, pos + t.size()) // find next occurrence of f
);
}
/*!
@param[in] reference_string the reference string to the current value
@param[in] value the value to consider
@param[in,out] result the result object to insert values to
@note Empty objects or arrays are flattened to `null`.
*/
static void flatten(const std::string reference_string,
const basic_json& value,
basic_json& result)
{
switch (value.m_type)
{
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)
{
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)
{
// 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);
}
}
break;
}
default:
{
// add primitive value with its reference string
result[reference_string] = value;
break;
}
}
}
/*!
@param[in] value flattened JSON
@return unflattened JSON
*/
static basic_json unflatten(const basic_json& value)
{
if (not value.is_object())
{
throw std::domain_error("only objects can be unflattened");
}
basic_json result;
// iterate the JSON object values
for (const auto& element : *value.m_value.object)
{
if (not element.second.is_primitive())
{
throw std::domain_error("values in object must be primitive");
}
// assign value to reference pointed to by JSON pointer;
// Note that if the JSON pointer is "" (i.e., points to the
// whole value), function get_and_create returns a reference
// to result itself. An assignment will then create a
// primitive value.
json_pointer(element.first).get_and_create(result) = element.second;
}
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` and will not be
reconstructed correctly by the @ref unflatten() function.
@complexity Linear in the size the JSON value.
@liveexample{The following code shows how a JSON object is flattened to an
object whose keys consist of JSON pointers.,flatten}
@sa @ref unflatten() for the reverse function
@since version 2.0.0
*/
basic_json flatten() const
{
basic_json result(value_t::object);
json_pointer::flatten("", *this, result);
return result;
}
/*!
@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 unflatten() const
{
return json_pointer::unflatten(*this);
}
/// @}
};
@ -8185,9 +8864,9 @@ struct hash<nlohmann::json>
/*!
@brief user-defined string literal for JSON values
This operator implements a user-defined string literal for JSON objects. It can
be used by adding \p "_json" to a string literal and returns a JSON object if
no parse error occurred.
This operator implements a user-defined string literal for JSON objects. It
can be used by adding \p "_json" to a string literal and returns a JSON object
if no parse error occurred.
@param[in] s a string representation of a JSON object
@return a JSON object
@ -8199,6 +8878,16 @@ inline nlohmann::json operator "" _json(const char* s, std::size_t)
return nlohmann::json::parse(reinterpret_cast<const nlohmann::json::string_t::value_type*>(s));
}
/*!
@brief user-defined string literal for JSON pointer
@since version 2.0.0
*/
inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t)
{
return nlohmann::json::json_pointer(s);
}
// restore GCC/clang diagnostic settings
#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__)
#pragma GCC diagnostic pop

View file

@ -12052,6 +12052,345 @@ 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"(
{
"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(j[json::json_pointer()] == j);
CHECK(j[json::json_pointer("")] == j);
// array access
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]);
// checked array access
CHECK(j.at(json::json_pointer("/foo/0")) == j["foo"][0]);
CHECK(j.at(json::json_pointer("/foo/1")) == j["foo"][1]);
// empty string access
CHECK(j[json::json_pointer("/")] == j[""]);
// other cases
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"]);
// checked access
CHECK(j.at(json::json_pointer("/ ")) == j[" "]);
CHECK(j.at(json::json_pointer("/c%d")) == j["c%d"]);
CHECK(j.at(json::json_pointer("/e^f")) == j["e^f"]);
CHECK(j.at(json::json_pointer("/g|h")) == j["g|h"]);
CHECK(j.at(json::json_pointer("/i\\j")) == j["i\\j"]);
CHECK(j.at(json::json_pointer("/k\"l")) == j["k\"l"]);
// escaped access
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(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({{"a", {{"b", 42}}}})[json::json_pointer("/a/b")] == json(42));
// unresolved access
json j_primitive = 1;
CHECK_THROWS_AS(j_primitive["/foo"_json_pointer], std::out_of_range);
CHECK_THROWS_WITH(j_primitive["/foo"_json_pointer], "unresolved reference token 'foo'");
CHECK_THROWS_AS(j_primitive.at("/foo"_json_pointer), std::out_of_range);
CHECK_THROWS_WITH(j_primitive.at("/foo"_json_pointer), "unresolved reference token 'foo'");
}
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(j[json::json_pointer()] == j);
CHECK(j[json::json_pointer("")] == j);
// array access
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]);
// checked array access
CHECK(j.at(json::json_pointer("/foo/0")) == j["foo"][0]);
CHECK(j.at(json::json_pointer("/foo/1")) == j["foo"][1]);
// empty string access
CHECK(j[json::json_pointer("/")] == j[""]);
// other cases
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"]);
// checked access
CHECK(j.at(json::json_pointer("/ ")) == j[" "]);
CHECK(j.at(json::json_pointer("/c%d")) == j["c%d"]);
CHECK(j.at(json::json_pointer("/e^f")) == j["e^f"]);
CHECK(j.at(json::json_pointer("/g|h")) == j["g|h"]);
CHECK(j.at(json::json_pointer("/i\\j")) == j["i\\j"]);
CHECK(j.at(json::json_pointer("/k\"l")) == j["k\"l"]);
// escaped access
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(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");
// unresolved access
const json j_primitive = 1;
CHECK_THROWS_AS(j_primitive["/foo"_json_pointer], std::out_of_range);
CHECK_THROWS_WITH(j_primitive["/foo"_json_pointer], "unresolved reference token 'foo'");
CHECK_THROWS_AS(j_primitive.at("/foo"_json_pointer), std::out_of_range);
CHECK_THROWS_WITH(j_primitive.at("/foo"_json_pointer), "unresolved reference token 'foo'");
}
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(j[""_json_pointer] == j);
// array access
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("array access")
{
SECTION("nonconst access")
{
json j = {1, 2, 3};
const json j_const = j;
// 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 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}));
// error with leading 0
CHECK_THROWS_AS(j["/01"_json_pointer], std::domain_error);
CHECK_THROWS_WITH(j["/01"_json_pointer], "array index must not begin with '0'");
CHECK_THROWS_AS(j_const["/01"_json_pointer], std::domain_error);
CHECK_THROWS_WITH(j_const["/01"_json_pointer], "array index must not begin with '0'");
CHECK_THROWS_AS(j.at("/01"_json_pointer), std::domain_error);
CHECK_THROWS_WITH(j.at("/01"_json_pointer), "array index must not begin with '0'");
CHECK_THROWS_AS(j_const.at("/01"_json_pointer), std::domain_error);
CHECK_THROWS_WITH(j_const.at("/01"_json_pointer), "array index must not begin with '0'");
// error with incorrect numbers
CHECK_THROWS_AS(j["/one"_json_pointer] = 1, std::invalid_argument);
// assign to "-"
j["/-"_json_pointer] = 99;
CHECK(j == json({1, 13, 3, 33, nullptr, 55, 99}));
// error when using "-" in const object
CHECK_THROWS_AS(j_const["/-"_json_pointer], std::out_of_range);
CHECK_THROWS_WITH(j_const["/-"_json_pointer], "array index '-' (3) is out of range");
// error when using "-" with at
CHECK_THROWS_AS(j.at("/-"_json_pointer), std::out_of_range);
CHECK_THROWS_WITH(j.at("/-"_json_pointer), "array index '-' (7) is out of range");
CHECK_THROWS_AS(j_const.at("/-"_json_pointer), std::out_of_range);
CHECK_THROWS_WITH(j_const.at("/-"_json_pointer), "array index '-' (3) is out of range");
}
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")
{
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"}
}
}
};
json j_flatten =
{
{"/pi", 3.141},
{"/happy", true},
{"/name", "Niels"},
{"/nothing", nullptr},
{"/answer/everything", 42},
{"/list/0", 1},
{"/list/1", 0},
{"/list/2", 2},
{"/object/currency", "USD"},
{"/object/value", 42.99},
{"/object/", "empty string"},
{"/object/~1", "slash"},
{"/object/~0", "tilde"},
{"/object/~01", "tilde1"}
};
// check if flattened result is as expected
CHECK(j.flatten() == j_flatten);
// check if unflattened result is as expected
CHECK(j_flatten.unflatten() == j);
// error for nonobjects
CHECK_THROWS_AS(json(1).unflatten(), std::domain_error);
CHECK_THROWS_WITH(json(1).unflatten(), "only objects can be unflattened");
// error for nonprimitve values
CHECK_THROWS_AS(json({{"/1", {1, 2, 3}}}).unflatten(), std::domain_error);
CHECK_THROWS_WITH(json({{"/1", {1, 2, 3}}}).unflatten(), "values in object must be primitive");
// error for conflicting values
json j_error = {{"", 42}, {"/foo", 17}};
CHECK_THROWS_AS(j_error.unflatten(), std::domain_error);
CHECK_THROWS_WITH(j_error.unflatten(), "invalid value to unflatten");
// explicit roundtrip check
CHECK(j.flatten().unflatten() == j);
// roundtrip for primitive values
json j_null;
CHECK(j_null.flatten().unflatten() == j_null);
json j_number = 42;
CHECK(j_number.flatten().unflatten() == j_number);
json j_boolean = false;
CHECK(j_boolean.flatten().unflatten() == j_boolean);
json j_string = "foo";
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());
}
}
TEST_CASE("regression tests")
{
SECTION("issue #60 - Double quotation mark is not parsed correctly")
@ -12419,4 +12758,3 @@ TEST_CASE("regression tests")
CHECK(dest == expected);
}
}