Add JSON Pointer for On Demand (#1615)

* Add working JSON pointer for array of atoms.

* Add working JSON pointer for object with key-atom pairs.

* Add first version of JSON pointer.

* Update tests (2 tests).

* Make tests exceptionless.

* Fix builing issues.

* Add more tests. Add json_pointer validation in array-inl.h and object-inl.h and empty json_pointer in document-inl.h.

* Fix errors in tests.

* Review.

* Add missing comment.
This commit is contained in:
Nicolas Boyer 2021-06-11 14:20:05 -04:00 committed by GitHub
parent 40cba172ed
commit a4803d50c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 425 additions and 2 deletions

View File

@ -93,6 +93,51 @@ simdjson_really_inline simdjson_result<size_t> array::count_elements() & noexcep
return count;
}
inline simdjson_result<value> array::at_pointer(std::string_view json_pointer) noexcept {
if (json_pointer[0] != '/') { return INVALID_JSON_POINTER; }
json_pointer = json_pointer.substr(1);
// - means "the append position" or "the element after the end of the array"
// We don't support this, because we're returning a real element, not a position.
if (json_pointer == "-") { return INDEX_OUT_OF_BOUNDS; }
// Read the array index
size_t array_index = 0;
size_t i;
for (i = 0; i < json_pointer.length() && json_pointer[i] != '/'; i++) {
uint8_t digit = uint8_t(json_pointer[i] - '0');
// Check for non-digit in array index. If it's there, we're trying to get a field in an object
if (digit > 9) { return INCORRECT_TYPE; }
array_index = array_index*10 + digit;
}
// 0 followed by other digits is invalid
if (i > 1 && json_pointer[0] == '0') { return INVALID_JSON_POINTER; } // "JSON pointer array index has other characters after 0"
// Empty string is invalid; so is a "/" with no digits before it
if (i == 0) { return INVALID_JSON_POINTER; } // "Empty string in JSON pointer array index"
// Get the child
auto child = at(array_index);
// If there is an error, it ends here
if(child.error()) {
return child;
}
// If there is a /, we're not done yet, call recursively.
if (i < json_pointer.length()) {
child = child.at_pointer(json_pointer.substr(i));
}
return child;
}
simdjson_really_inline simdjson_result<value> array::at(size_t index) noexcept {
size_t i=0;
for (auto value : *this) {
if (i == index) { return value; }
i++;
}
return INDEX_OUT_OF_BOUNDS;
}
} // namespace ondemand
} // namespace SIMDJSON_IMPLEMENTATION
} // namespace simdjson
@ -126,4 +171,8 @@ simdjson_really_inline simdjson_result<size_t> simdjson_result<SIMDJSON_IMPLEME
if (error()) { return error(); }
return first.count_elements();
}
simdjson_really_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::array>::at_pointer(std::string_view json_pointer) noexcept {
if (error()) { return error(); }
return first.at_pointer(json_pointer);
}
} // namespace simdjson

View File

@ -43,6 +43,27 @@ public:
* safe to continue.
*/
simdjson_really_inline simdjson_result<size_t> count_elements() & noexcept;
/**
* Get the value associated with the given JSON pointer. We use the RFC 6901
* https://tools.ietf.org/html/rfc6901 standard, interpreting the current node
* as the root of its own JSON document.
*
* ondemand::parser parser;
* auto json = R"([ { "foo": { "a": [ 10, 20, 30 ] }} ])"_padded;
* auto doc = parser.iterate(json);
* doc.at_pointer("/0/foo/a/1") == 20
*
* Note that at_pointer() does not automatically rewind between each call (call rewind() to reset).
* Also note that at_pointer() relies on find_field() which implies that we do not unescape keys when matching.
* @return The value associated with the given JSON pointer, or:
* - NO_SUCH_FIELD if a field does not exist in an object
* - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length
* - INCORRECT_TYPE if a non-integer is used to access an array
* - INVALID_JSON_POINTER if the JSON pointer is invalid and cannot be parsed
*/
inline simdjson_result<value> at_pointer(std::string_view json_pointer) noexcept;
protected:
/**
* Begin array iteration.
@ -80,6 +101,15 @@ protected:
*/
simdjson_really_inline array(const value_iterator &iter) noexcept;
/**
* Get the value at the given index. This function has linear-time complexity.
* This function should only be called once as the array iterator is not reset between each call.
*
* @return The value at the given index, or:
* - INDEX_OUT_OF_BOUNDS if the array index is larger than an array length
*/
simdjson_really_inline simdjson_result<value> at(size_t index) noexcept;
/**
* Iterator marking current position.
*
@ -110,6 +140,7 @@ public:
simdjson_really_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::array_iterator> begin() noexcept;
simdjson_really_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::array_iterator> end() noexcept;
simdjson_really_inline simdjson_result<size_t> count_elements() & noexcept;
simdjson_really_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> at_pointer(std::string_view json_pointer) noexcept;
};
} // namespace simdjson

View File

@ -134,6 +134,23 @@ simdjson_really_inline simdjson_result<std::string_view> document::raw_json_toke
return std::string_view(reinterpret_cast<const char*>(_iter.peek_start()), _iter.peek_start_length());
}
simdjson_really_inline simdjson_result<value> document::at_pointer(std::string_view json_pointer) noexcept {
if (json_pointer.empty()) {
return this->resume_value();
}
json_type t;
SIMDJSON_TRY(type().get(t));
switch (t)
{
case json_type::array:
return (*this).get_array().at_pointer(json_pointer);
case json_type::object:
return (*this).get_object().at_pointer(json_pointer);
default:
return INVALID_JSON_POINTER;
}
}
} // namespace ondemand
} // namespace SIMDJSON_IMPLEMENTATION
} // namespace simdjson
@ -311,4 +328,9 @@ simdjson_really_inline simdjson_result<std::string_view> simdjson_result<SIMDJSO
return first.raw_json_token();
}
simdjson_really_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::at_pointer(std::string_view json_pointer) noexcept {
if (error()) { return error(); }
return first.at_pointer(json_pointer);
}
} // namespace simdjson

View File

@ -318,6 +318,34 @@ public:
* Returns debugging information.
*/
inline std::string to_debug_string() noexcept;
/**
* Get the value associated with the given JSON pointer. We use the RFC 6901
* https://tools.ietf.org/html/rfc6901 standard.
*
* ondemand::parser parser;
* auto json = R"({ "foo": { "a": [ 10, 20, 30 ] }})"_padded;
* auto doc = parser.iterate(json);
* doc.at_pointer("/foo/a/1") == 20
*
* It is allowed for a key to be the empty string:
*
* ondemand::parser parser;
* auto json = R"({ "": { "a": [ 10, 20, 30 ] }})"_padded;
* auto doc = parser.iterate(json);
* doc.at_pointer("//a/1") == 20
*
* Note that at_pointer() does not automatically rewind between each call (call rewind() to reset).
* Also note that at_pointer() relies on find_field() which implies that we do not unescape keys when matching
*
* @return The value associated with the given JSON pointer, or:
* - NO_SUCH_FIELD if a field does not exist in an object
* - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length
* - INCORRECT_TYPE if a non-integer is used to access an array
* - INVALID_JSON_POINTER if the JSON pointer is invalid and cannot be parsed
*/
simdjson_really_inline simdjson_result<value> at_pointer(std::string_view json_pointer) noexcept;
protected:
simdjson_really_inline document(ondemand::json_iterator &&iter) noexcept;
simdjson_really_inline const uint8_t *text(uint32_t idx) const noexcept;
@ -396,6 +424,8 @@ public:
/** @copydoc simdjson_really_inline std::string_view document::raw_json_token() const noexcept */
simdjson_really_inline simdjson_result<std::string_view> raw_json_token() noexcept;
simdjson_really_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> at_pointer(std::string_view json_pointer) noexcept;
};
} // namespace simdjson

View File

@ -68,6 +68,46 @@ simdjson_really_inline simdjson_result<object_iterator> object::end() noexcept {
return object_iterator(iter);
}
inline simdjson_result<value> object::at_pointer(std::string_view json_pointer) noexcept {
if (json_pointer[0] != '/') { return INVALID_JSON_POINTER; }
json_pointer = json_pointer.substr(1);
size_t slash = json_pointer.find('/');
std::string_view key = json_pointer.substr(0, slash);
// Grab the child with the given key
simdjson_result<value> child;
// If there is an escape character in the key, unescape it and then get the child.
size_t escape = key.find('~');
if (escape != std::string_view::npos) {
// Unescape the key
std::string unescaped(key);
do {
switch (unescaped[escape+1]) {
case '0':
unescaped.replace(escape, 2, "~");
break;
case '1':
unescaped.replace(escape, 2, "/");
break;
default:
return INVALID_JSON_POINTER; // "Unexpected ~ escape character in JSON pointer");
}
escape = unescaped.find('~', escape+1);
} while (escape != std::string::npos);
child = find_field(unescaped); // Take note find_field does not unescape keys when matching
} else {
child = find_field(key);
}
if(child.error()) {
return child; // we do not continue if there was an error
}
// If there is a /, we have to recurse and look up more of the path
if (slash != std::string_view::npos) {
child = child.at_pointer(json_pointer.substr(slash));
}
return child;
}
} // namespace ondemand
} // namespace SIMDJSON_IMPLEMENTATION
} // namespace simdjson
@ -112,4 +152,9 @@ simdjson_really_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value>
return std::forward<SIMDJSON_IMPLEMENTATION::ondemand::object>(first).find_field(key);
}
simdjson_really_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::object>::at_pointer(std::string_view json_pointer) noexcept {
if (error()) { return error(); }
return first.at_pointer(json_pointer);
}
} // namespace simdjson

View File

@ -74,6 +74,34 @@ public:
/** @overload simdjson_really_inline simdjson_result<value> find_field_unordered(std::string_view key) & noexcept; */
simdjson_really_inline simdjson_result<value> operator[](std::string_view key) && noexcept;
/**
* Get the value associated with the given JSON pointer. We use the RFC 6901
* https://tools.ietf.org/html/rfc6901 standard, interpreting the current node
* as the root of its own JSON document.
*
* ondemand::parser parser;
* auto json = R"({ "foo": { "a": [ 10, 20, 30 ] }})"_padded;
* auto doc = parser.iterate(json);
* doc.at_pointer("/foo/a/1") == 20
*
* It is allowed for a key to be the empty string:
*
* ondemand::parser parser;
* auto json = R"({ "": { "a": [ 10, 20, 30 ] }})"_padded;
* auto doc = parser.iterate(json);
* doc.at_pointer("//a/1") == 20
*
* Note that at_pointer() does not automatically rewind between each call (call rewind() to reset).
* Also note that at_pointer() relies on find_field() which implies that we do not unescape keys when matching.
*
* @return The value associated with the given JSON pointer, or:
* - NO_SUCH_FIELD if a field does not exist in an object
* - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length
* - INCORRECT_TYPE if a non-integer is used to access an array
* - INVALID_JSON_POINTER if the JSON pointer is invalid and cannot be parsed
*/
inline simdjson_result<value> at_pointer(std::string_view json_pointer) noexcept;
protected:
static simdjson_really_inline simdjson_result<object> start(value_iterator &iter) noexcept;
static simdjson_really_inline simdjson_result<object> start_root(value_iterator &iter) noexcept;
@ -111,6 +139,7 @@ public:
simdjson_really_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> find_field_unordered(std::string_view key) && noexcept;
simdjson_really_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> operator[](std::string_view key) & noexcept;
simdjson_really_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> operator[](std::string_view key) && noexcept;
simdjson_really_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> at_pointer(std::string_view json_pointer) noexcept;
};
} // namespace simdjson

View File

@ -135,6 +135,20 @@ simdjson_really_inline std::string_view value::raw_json_token() noexcept {
return std::string_view(reinterpret_cast<const char*>(iter.peek_start()), iter.peek_start_length());
}
simdjson_really_inline simdjson_result<value> value::at_pointer(std::string_view json_pointer) noexcept {
json_type t;
SIMDJSON_TRY(type().get(t));
switch (t)
{
case json_type::array:
return (*this).get_array().at_pointer(json_pointer);
case json_type::object:
return (*this).get_object().at_pointer(json_pointer);
default:
return INVALID_JSON_POINTER;
}
}
} // namespace ondemand
} // namespace SIMDJSON_IMPLEMENTATION
} // namespace simdjson
@ -296,4 +310,9 @@ simdjson_really_inline simdjson_result<std::string_view> simdjson_result<SIMDJSO
return first.raw_json_token();
}
simdjson_really_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value>::at_pointer(std::string_view json_pointer) noexcept {
if (error()) { return error(); }
return first.at_pointer(json_pointer);
}
} // namespace simdjson

View File

@ -314,6 +314,33 @@ public:
*/
simdjson_really_inline std::string_view raw_json_token() noexcept;
/**
* Get the value associated with the given JSON pointer. We use the RFC 6901
* https://tools.ietf.org/html/rfc6901 standard.
*
* ondemand::parser parser;
* auto json = R"({ "foo": { "a": [ 10, 20, 30 ] }})"_padded;
* auto doc = parser.iterate(json);
* doc.at_pointer("/foo/a/1") == 20
*
* It is allowed for a key to be the empty string:
*
* ondemand::parser parser;
* auto json = R"({ "": { "a": [ 10, 20, 30 ] }})"_padded;
* auto doc = parser.iterate(json);
* doc.at_pointer("//a/1") == 20
*
* Note that at_pointer() does not automatically rewind between each call (call rewind() to reset).
* Also note that at_pointer() relies on find_field() which implies that we do not unescape keys when matching
*
* @return The value associated with the given JSON pointer, or:
* - NO_SUCH_FIELD if a field does not exist in an object
* - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length
* - INCORRECT_TYPE if a non-integer is used to access an array
* - INVALID_JSON_POINTER if the JSON pointer is invalid and cannot be parsed
*/
simdjson_really_inline simdjson_result<value> at_pointer(std::string_view json_pointer) noexcept;
protected:
/**
* Create a value.
@ -459,6 +486,8 @@ public:
/** @copydoc simdjson_really_inline std::string_view value::raw_json_token() const noexcept */
simdjson_really_inline simdjson_result<std::string_view> raw_json_token() noexcept;
simdjson_really_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> at_pointer(std::string_view json_pointer) noexcept;
};
} // namespace simdjson

View File

@ -2,14 +2,15 @@
link_libraries(simdjson)
include_directories(..)
add_subdirectory(compilation_failure_tests)
add_cpp_test(ondemand_tostring_tests LABELS ondemand acceptance per_implementation)
add_cpp_test(ondemand_tostring_tests LABELS ondemand acceptance per_implementation)
add_cpp_test(ondemand_active_tests LABELS ondemand acceptance per_implementation)
add_cpp_test(ondemand_array_tests LABELS ondemand acceptance per_implementation)
add_cpp_test(ondemand_array_error_tests LABELS ondemand acceptance per_implementation)
add_cpp_test(ondemand_compilation_tests LABELS ondemand acceptance per_implementation)
add_cpp_test(ondemand_error_tests LABELS ondemand acceptance per_implementation)
add_cpp_test(ondemand_json_pointer_tests LABELS ondemand acceptance per_implementation)
add_cpp_test(ondemand_key_string_tests LABELS ondemand acceptance per_implementation)
add_cpp_test(ondemand_misc_tests LABELS ondemand acceptance per_implementation)
add_cpp_test(ondemand_misc_tests LABELS ondemand acceptance per_implementation)
add_cpp_test(ondemand_number_tests LABELS ondemand acceptance per_implementation)
add_cpp_test(ondemand_object_tests LABELS ondemand acceptance per_implementation)
add_cpp_test(ondemand_object_error_tests LABELS ondemand acceptance per_implementation)

View File

@ -0,0 +1,168 @@
#include "simdjson.h"
#include "test_ondemand.h"
#include <string>
using namespace simdjson;
namespace json_pointer_tests {
const padded_string TEST_JSON = R"(
{
"/~01abc": [
0,
{
"\\\" 0": [
"value0",
"value1"
]
}
],
"0": "0 ok",
"01": "01 ok",
"": "empty ok",
"arr": []
}
)"_padded;
const padded_string TEST_RFC_JSON = 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
}
)"_padded;
bool run_success_test(const padded_string & json,std::string_view json_pointer,std::string expected) {
TEST_START();
ondemand::parser parser;
ondemand::document doc;
ondemand::value val;
std::string actual;
ASSERT_SUCCESS(parser.iterate(json).get(doc));
ASSERT_SUCCESS(doc.at_pointer(json_pointer).get(val));
ASSERT_SUCCESS(simdjson::to_string(val).get(actual));
ASSERT_EQUAL(actual,expected);
TEST_SUCCEED();
}
bool run_failure_test(const padded_string & json,std::string_view json_pointer,error_code expected) {
TEST_START();
ondemand::parser parser;
ondemand::document doc;
ASSERT_SUCCESS(parser.iterate(json).get(doc));
ASSERT_EQUAL(doc.at_pointer(json_pointer).error(),expected);
TEST_SUCCEED();
}
bool demo_test() {
TEST_START();
auto cars_json = R"( [
{ "make": "Toyota", "model": "Camry", "year": 2018, "tire_pressure": [ 40.1, 39.9, 37.7, 40.4 ] },
{ "make": "Kia", "model": "Soul", "year": 2012, "tire_pressure": [ 30.1, 31.0, 28.6, 28.7 ] },
{ "make": "Toyota", "model": "Tercel", "year": 1999, "tire_pressure": [ 29.8, 30.0, 30.2, 30.5 ] }
] )"_padded;
ondemand::parser parser;
ondemand::document cars;
double x;
ASSERT_SUCCESS(parser.iterate(cars_json).get(cars));
ASSERT_SUCCESS(cars.at_pointer("/0/tire_pressure/1").get(x));
ASSERT_EQUAL(x,39.9);
TEST_SUCCEED();
}
bool demo_relative_path() {
TEST_START();
auto cars_json = R"( [
{ "make": "Toyota", "model": "Camry", "year": 2018, "tire_pressure": [ 40.1, 39.9, 37.7, 40.4 ] },
{ "make": "Kia", "model": "Soul", "year": 2012, "tire_pressure": [ 30.1, 31.0, 28.6, 28.7 ] },
{ "make": "Toyota", "model": "Tercel", "year": 1999, "tire_pressure": [ 29.8, 30.0, 30.2, 30.5 ] }
] )"_padded;
ondemand::parser parser;
ondemand::document cars;
std::vector<double> measured;
ASSERT_SUCCESS(parser.iterate(cars_json).get(cars));
for (auto car_element : cars) {
double x;
ASSERT_SUCCESS(car_element.at_pointer("/tire_pressure/1").get(x));
measured.push_back(x);
}
std::vector<double> expected = {39.9, 31, 30};
if (measured != expected) { return false; }
TEST_SUCCEED();
}
bool many_json_pointers() {
TEST_START();
auto cars_json = R"( [
{ "make": "Toyota", "model": "Camry", "year": 2018, "tire_pressure": [ 40.1, 39.9, 37.7, 40.4 ] },
{ "make": "Kia", "model": "Soul", "year": 2012, "tire_pressure": [ 30.1, 31.0, 28.6, 28.7 ] },
{ "make": "Toyota", "model": "Tercel", "year": 1999, "tire_pressure": [ 29.8, 30.0, 30.2, 30.5 ] }
] )"_padded;
ondemand::parser parser;
ondemand::document cars;
std::vector<double> measured;
ASSERT_SUCCESS(parser.iterate(cars_json).get(cars));
for (int i = 0; i < 3; i++) {
double x;
std::string json_pointer = "/" + std::to_string(i) + "/tire_pressure/1";
ASSERT_SUCCESS(cars.at_pointer(json_pointer).get(x));
measured.push_back(x);
cars.rewind();
}
std::vector<double> expected = {39.9, 31, 30};
if (measured != expected) { return false; }
TEST_SUCCEED();
}
bool run() {
return
demo_test() &&
demo_relative_path() &&
run_success_test(TEST_RFC_JSON,"",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})") &&
run_success_test(TEST_RFC_JSON,"/foo",R"(["bar","baz"])") &&
run_success_test(TEST_RFC_JSON,"/foo/0",R"("bar")") &&
run_success_test(TEST_RFC_JSON,"/",R"(0)") &&
run_success_test(TEST_RFC_JSON,"/a~1b",R"(1)") &&
run_success_test(TEST_RFC_JSON,"/c%d",R"(2)") &&
run_success_test(TEST_RFC_JSON,"/e^f",R"(3)") &&
run_success_test(TEST_RFC_JSON,"/g|h",R"(4)") &&
run_success_test(TEST_RFC_JSON,R"(/i\\j)",R"(5)") &&
run_success_test(TEST_RFC_JSON,R"(/k\"l)",R"(6)") &&
run_success_test(TEST_RFC_JSON,"/ ",R"(7)") &&
run_success_test(TEST_RFC_JSON,"/m~0n",R"(8)") &&
run_success_test(TEST_JSON, "", R"({"/~01abc":[0,{"\\\" 0":["value0","value1"]}],"0":"0 ok","01":"01 ok","":"empty ok","arr":[]})") &&
run_success_test(TEST_JSON, R"(/~1~001abc)", R"([0,{"\\\" 0":["value0","value1"]}])") &&
run_success_test(TEST_JSON, R"(/~1~001abc/1)", R"({"\\\" 0":["value0","value1"]})") &&
run_success_test(TEST_JSON, R"(/~1~001abc/1/\\\" 0)", R"(["value0","value1"])") &&
run_success_test(TEST_JSON, R"(/~1~001abc/1/\\\" 0/0)", "\"value0\"") &&
run_success_test(TEST_JSON, R"(/~1~001abc/1/\\\" 0/1)", "\"value1\"") &&
run_success_test(TEST_JSON, "/arr", R"([])") &&
run_success_test(TEST_JSON, "/0", "\"0 ok\"") &&
run_success_test(TEST_JSON, "/01", "\"01 ok\"") &&
run_success_test(TEST_JSON, "", R"({"/~01abc":[0,{"\\\" 0":["value0","value1"]}],"0":"0 ok","01":"01 ok","":"empty ok","arr":[]})") &&
run_failure_test(TEST_JSON, R"(/~1~001abc/1/\\\" 0/2)", INDEX_OUT_OF_BOUNDS) &&
run_failure_test(TEST_JSON, "/arr/0", INDEX_OUT_OF_BOUNDS) &&
run_failure_test(TEST_JSON, "~1~001abc", INVALID_JSON_POINTER) &&
run_failure_test(TEST_JSON, "/~01abc", NO_SUCH_FIELD) &&
run_failure_test(TEST_JSON, "/~1~001abc/01", INVALID_JSON_POINTER) &&
run_failure_test(TEST_JSON, "/~1~001abc/", INVALID_JSON_POINTER) &&
run_failure_test(TEST_JSON, "/~1~001abc/-", INDEX_OUT_OF_BOUNDS) &&
many_json_pointers() &&
true;
}
} // json_pointer_tests
int main(int argc, char *argv[]) {
return test_main(argc, argv, json_pointer_tests::run);
}