Merge pull request #644 from simdjson/jkeiser/indexer

Make object[] key lookup
This commit is contained in:
John Keiser 2020-03-28 16:46:53 -07:00 committed by GitHub
commit ae24fe3850
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 80 additions and 121 deletions

View File

@ -63,6 +63,10 @@ Once you have an element, you can navigate it with idiomatic C++ iterators, oper
* **Array Iteration:** To iterate through an array, use `for (auto value : array) { ... }`. If you
know the type of the value, you can cast it right there, too! `for (double value : array) { ... }`
* **Object Iteration:** You can iterate through an object's fields, too: `for (auto [key, value] : object)`
* **Array Index:** To get at an array value by index, use the at() method: `array.at(0)` gets the
first element.
> Note that array[0] does not compile, because implementing [] gives the impression indexing is a
> O(1) operation, which it is not presently in simdjson.
Here are some examples of all of the above:
@ -98,10 +102,13 @@ for (dom::object car : cars) {
}
```
JSON Pointer
------------
The simdjson library also supports JSON pointer, letting you reach further down into the document:
The simdjson library also supports [JSON pointer](https://tools.ietf.org/html/rfc6901) through the
at() method, letting you reach further down into the document in a single call:
```c++
auto cars_json = R"( [
@ -111,7 +118,7 @@ auto cars_json = R"( [
] )"_padded;
dom::parser parser;
dom::element cars = parser.parse(cars_json);
cout << cars["/0/tire_pressure/1"] << endl; // Prints 39.9
cout << cars.at("0/tire_pressure/1") << endl; // Prints 39.9
```
Error Handling

View File

@ -251,38 +251,34 @@ public:
#endif // SIMDJSON_EXCEPTIONS
/**
* Get the value associated with the given JSON pointer.
* Get the value associated with the given key.
*
* The key will be matched against **unescaped** JSON:
*
* dom::parser parser;
* element doc = parser.parse(R"({ "foo": { "a": [ 10, 20, 30 ] }})");
* doc["/foo/a/1"] == 20
* doc["/"]["foo"]["a"].at(1) == 20
* doc[""]["foo"]["a"].at(1) == 20
* parser.parse(R"({ "a\n": 1 })")["a\n"].get<uint64_t>().value == 1
* parser.parse(R"({ "a\n": 1 })")["a\\n"].get<uint64_t>().error == NO_SUCH_FIELD
*
* @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
* @return The value associated with this field, or:
* - NO_SUCH_FIELD if the field does not exist in the object
* - INCORRECT_TYPE if this is not an object
*/
inline simdjson_result<element> operator[](const std::string_view &json_pointer) const noexcept;
inline simdjson_result<element> operator[](const std::string_view &key) const noexcept;
/**
* Get the value associated with the given JSON pointer.
* Get the value associated with the given key.
*
* The key will be matched against **unescaped** JSON:
*
* dom::parser parser;
* element doc = parser.parse(R"({ "foo": { "a": [ 10, 20, 30 ] }})");
* doc["/foo/a/1"] == 20
* doc["/"]["foo"]["a"].at(1) == 20
* doc[""]["foo"]["a"].at(1) == 20
* parser.parse(R"({ "a\n": 1 })")["a\n"].get<uint64_t>().value == 1
* parser.parse(R"({ "a\n": 1 })")["a\\n"].get<uint64_t>().error == NO_SUCH_FIELD
*
* @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
* @return The value associated with this field, or:
* - NO_SUCH_FIELD if the field does not exist in the object
* - INCORRECT_TYPE if this is not an object
*/
inline simdjson_result<element> operator[](const char *json_pointer) const noexcept;
inline simdjson_result<element> operator[](const char *key) const noexcept;
/**
* Get the value associated with the given JSON pointer.
@ -390,38 +386,6 @@ public:
*/
inline iterator end() const noexcept;
/**
* Get the value associated with the given JSON pointer.
*
* dom::parser parser;
* array a = parser.parse(R"([ { "foo": { "a": [ 10, 20, 30 ] }} ])");
* a["0/foo/a/1"] == 20
* a["0"]["foo"]["a"].at(1) == 20
*
* @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<element> operator[](const std::string_view &json_pointer) const noexcept;
/**
* Get the value associated with the given JSON pointer.
*
* dom::parser parser;
* array a = parser.parse(R"([ { "foo": { "a": [ 10, 20, 30 ] }} ])");
* a["0/foo/a/1"] == 20
* a["0"]["foo"]["a"].at(1) == 20
*
* @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<element> operator[](const char *json_pointer) const noexcept;
/**
* Get the value associated with the given JSON pointer.
*
@ -511,36 +475,34 @@ public:
inline iterator end() const noexcept;
/**
* Get the value associated with the given JSON pointer.
* Get the value associated with the given key.
*
* The key will be matched against **unescaped** JSON:
*
* dom::parser parser;
* object obj = parser.parse(R"({ "foo": { "a": [ 10, 20, 30 ] }})");
* obj["foo/a/1"] == 20
* obj["foo"]["a"].at(1) == 20
* parser.parse(R"({ "a\n": 1 })")["a\n"].get<uint64_t>().value == 1
* parser.parse(R"({ "a\n": 1 })")["a\\n"].get<uint64_t>().error == NO_SUCH_FIELD
*
* @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
* @return The value associated with this field, or:
* - NO_SUCH_FIELD if the field does not exist in the object
* - INCORRECT_TYPE if this is not an object
*/
inline simdjson_result<element> operator[](const std::string_view &json_pointer) const noexcept;
inline simdjson_result<element> operator[](const std::string_view &key) const noexcept;
/**
* Get the value associated with the given JSON pointer.
* Get the value associated with the given key.
*
* The key will be matched against **unescaped** JSON:
*
* dom::parser parser;
* object obj = parser.parse(R"({ "foo": { "a": [ 10, 20, 30 ] }})");
* obj["foo/a/1"] == 20
* obj["foo"]["a"].at(1) == 20
* parser.parse(R"({ "a\n": 1 })")["a\n"].get<uint64_t>().value == 1
* parser.parse(R"({ "a\n": 1 })")["a\\n"].get<uint64_t>().error == NO_SUCH_FIELD
*
* @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
* @return The value associated with this field, or:
* - NO_SUCH_FIELD if the field does not exist in the object
* - INCORRECT_TYPE if this is not an object
*/
inline simdjson_result<element> operator[](const char *json_pointer) const noexcept;
inline simdjson_result<element> operator[](const char *key) const noexcept;
/**
* Get the value associated with the given JSON pointer.
@ -1467,8 +1429,8 @@ public:
template<typename T>
inline simdjson_result<T> get() const noexcept;
inline simdjson_result<dom::element> operator[](const std::string_view &json_pointer) const noexcept;
inline simdjson_result<dom::element> operator[](const char *json_pointer) const noexcept;
inline simdjson_result<dom::element> operator[](const std::string_view &key) const noexcept;
inline simdjson_result<dom::element> operator[](const char *key) const noexcept;
inline simdjson_result<dom::element> at(const std::string_view &json_pointer) const noexcept;
inline simdjson_result<dom::element> at(size_t index) const noexcept;
inline simdjson_result<dom::element> at_key(const std::string_view &key) const noexcept;
@ -1494,8 +1456,6 @@ public:
really_inline simdjson_result(dom::array value) noexcept;
really_inline simdjson_result(error_code error) noexcept;
inline simdjson_result<dom::element> operator[](const std::string_view &json_pointer) const noexcept;
inline simdjson_result<dom::element> operator[](const char *json_pointer) const noexcept;
inline simdjson_result<dom::element> at(const std::string_view &json_pointer) const noexcept;
inline simdjson_result<dom::element> at(size_t index) const noexcept;
@ -1513,8 +1473,8 @@ public:
really_inline simdjson_result(dom::object value) noexcept;
really_inline simdjson_result(error_code error) noexcept;
inline simdjson_result<dom::element> operator[](const std::string_view &json_pointer) const noexcept;
inline simdjson_result<dom::element> operator[](const char *json_pointer) const noexcept;
inline simdjson_result<dom::element> operator[](const std::string_view &key) const noexcept;
inline simdjson_result<dom::element> operator[](const char *key) const noexcept;
inline simdjson_result<dom::element> at(const std::string_view &json_pointer) const noexcept;
inline simdjson_result<dom::element> at_key(const std::string_view &key) const noexcept;
inline simdjson_result<dom::element> at_key_case_insensitive(const std::string_view &key) const noexcept;

View File

@ -38,28 +38,28 @@ inline simdjson_result<T> simdjson_result<dom::element>::get() const noexcept {
return first.get<T>();
}
inline simdjson_result<dom::element> simdjson_result<dom::element>::operator[](const std::string_view &json_pointer) const noexcept {
if (error()) { return *this; }
return first[json_pointer];
inline simdjson_result<dom::element> simdjson_result<dom::element>::operator[](const std::string_view &key) const noexcept {
if (error()) { return error(); }
return first[key];
}
inline simdjson_result<dom::element> simdjson_result<dom::element>::operator[](const char *json_pointer) const noexcept {
if (error()) { return *this; }
return first[json_pointer];
inline simdjson_result<dom::element> simdjson_result<dom::element>::operator[](const char *key) const noexcept {
if (error()) { return error(); }
return first[key];
}
inline simdjson_result<dom::element> simdjson_result<dom::element>::at(const std::string_view &json_pointer) const noexcept {
if (error()) { return *this; }
if (error()) { return error(); }
return first.at(json_pointer);
}
inline simdjson_result<dom::element> simdjson_result<dom::element>::at(size_t index) const noexcept {
if (error()) { return *this; }
if (error()) { return error(); }
return first.at(index);
}
inline simdjson_result<dom::element> simdjson_result<dom::element>::at_key(const std::string_view &key) const noexcept {
if (error()) { return *this; }
if (error()) { return error(); }
return first.at_key(key);
}
inline simdjson_result<dom::element> simdjson_result<dom::element>::at_key_case_insensitive(const std::string_view &key) const noexcept {
if (error()) { return *this; }
if (error()) { return error(); }
return first.at_key_case_insensitive(key);
}
@ -115,14 +115,6 @@ inline dom::array::iterator simdjson_result<dom::array>::end() const noexcept(fa
#endif // SIMDJSON_EXCEPTIONS
inline simdjson_result<dom::element> simdjson_result<dom::array>::operator[](const std::string_view &json_pointer) const noexcept {
if (error()) { return error(); }
return first.at(json_pointer);
}
inline simdjson_result<dom::element> simdjson_result<dom::array>::operator[](const char *json_pointer) const noexcept {
if (error()) { return error(); }
return first.at(json_pointer);
}
inline simdjson_result<dom::element> simdjson_result<dom::array>::at(const std::string_view &json_pointer) const noexcept {
if (error()) { return error(); }
return first.at(json_pointer);
@ -142,13 +134,13 @@ really_inline simdjson_result<dom::object>::simdjson_result(dom::object value) n
really_inline simdjson_result<dom::object>::simdjson_result(error_code error) noexcept
: internal::simdjson_result_base<dom::object>(error) {}
inline simdjson_result<dom::element> simdjson_result<dom::object>::operator[](const std::string_view &json_pointer) const noexcept {
inline simdjson_result<dom::element> simdjson_result<dom::object>::operator[](const std::string_view &key) const noexcept {
if (error()) { return error(); }
return first[json_pointer];
return first[key];
}
inline simdjson_result<dom::element> simdjson_result<dom::object>::operator[](const char *json_pointer) const noexcept {
inline simdjson_result<dom::element> simdjson_result<dom::object>::operator[](const char *key) const noexcept {
if (error()) { return error(); }
return first[json_pointer];
return first[key];
}
inline simdjson_result<dom::element> simdjson_result<dom::object>::at(const std::string_view &json_pointer) const noexcept {
if (error()) { return error(); }
@ -603,11 +595,11 @@ inline object::iterator object::end() const noexcept {
return iterator(doc, after_element() - 1);
}
inline simdjson_result<element> object::operator[](const std::string_view &json_pointer) const noexcept {
return at(json_pointer);
inline simdjson_result<element> object::operator[](const std::string_view &key) const noexcept {
return at_key(key);
}
inline simdjson_result<element> object::operator[](const char *json_pointer) const noexcept {
return at(json_pointer);
inline simdjson_result<element> object::operator[](const char *key) const noexcept {
return at_key(key);
}
inline simdjson_result<element> object::at(const std::string_view &json_pointer) const noexcept {
size_t slash = json_pointer.find('/');
@ -840,11 +832,11 @@ inline element::operator object() const noexcept(false) { return get<object>();
#endif
inline simdjson_result<element> element::operator[](const std::string_view &json_pointer) const noexcept {
return at(json_pointer);
inline simdjson_result<element> element::operator[](const std::string_view &key) const noexcept {
return at_key(key);
}
inline simdjson_result<element> element::operator[](const char *json_pointer) const noexcept {
return at(json_pointer);
inline simdjson_result<element> element::operator[](const char *key) const noexcept {
return at_key(key);
}
inline simdjson_result<element> element::at(const std::string_view &json_pointer) const noexcept {
switch (type()) {

View File

@ -805,14 +805,14 @@ namespace dom_api_tests {
bool document_object_index() {
std::cout << "Running " << __func__ << std::endl;
string json(R"({ "a": 1, "b": 2, "c": 3})");
string json(R"({ "a": 1, "b": 2, "c/d": 3})");
dom::parser parser;
auto [doc, error] = parser.parse(json);
if (doc["a"].get<uint64_t>().first != 1) { cerr << "Expected uint64_t(doc[\"a\"]) to be 1, was " << doc["a"].first << endl; return false; }
if (doc["b"].get<uint64_t>().first != 2) { cerr << "Expected uint64_t(doc[\"b\"]) to be 2, was " << doc["b"].first << endl; return false; }
if (doc["c"].get<uint64_t>().first != 3) { cerr << "Expected uint64_t(doc[\"c\"]) to be 3, was " << doc["c"].first << endl; return false; }
if (doc["c/d"].get<uint64_t>().first != 3) { cerr << "Expected uint64_t(doc[\"c/d\"]) to be 3, was " << doc["c"].first << endl; return false; }
// Check all three again in backwards order, to ensure we can go backwards
if (doc["c"].get<uint64_t>().first != 3) { cerr << "Expected uint64_t(doc[\"c\"]) to be 3, was " << doc["c"].first << endl; return false; }
if (doc["c/d"].get<uint64_t>().first != 3) { cerr << "Expected uint64_t(doc[\"c/d\"]) to be 3, was " << doc["c"].first << endl; return false; }
if (doc["b"].get<uint64_t>().first != 2) { cerr << "Expected uint64_t(doc[\"b\"]) to be 2, was " << doc["b"].first << endl; return false; }
if (doc["a"].get<uint64_t>().first != 1) { cerr << "Expected uint64_t(doc[\"a\"]) to be 1, was " << doc["a"].first << endl; return false; }
@ -825,7 +825,7 @@ namespace dom_api_tests {
bool object_index() {
std::cout << "Running " << __func__ << std::endl;
string json(R"({ "obj": { "a": 1, "b": 2, "c": 3 } })");
string json(R"({ "obj": { "a": 1, "b": 2, "c/d": 3 } })");
dom::parser parser;
auto [doc, error] = parser.parse(json);
if (error) { cerr << "Error: " << error << endl; return false; }
@ -839,9 +839,9 @@ namespace dom_api_tests {
obj["obj"].get<dom::object>().tie(obj, error); // tie(...) = fails with "no viable overloaded '='" on Apple clang version 11.0.0
if (obj["a"].get<uint64_t>().first != 1) { cerr << "Expected uint64_t(obj[\"a\"]) to be 1, was " << obj["a"].first << endl; return false; }
if (obj["b"].get<uint64_t>().first != 2) { cerr << "Expected uint64_t(obj[\"b\"]) to be 2, was " << obj["b"].first << endl; return false; }
if (obj["c"].get<uint64_t>().first != 3) { cerr << "Expected uint64_t(obj[\"c\"]) to be 3, was " << obj["c"].first << endl; return false; }
if (obj["c/d"].get<uint64_t>().first != 3) { cerr << "Expected uint64_t(obj[\"c\"]) to be 3, was " << obj["c"].first << endl; return false; }
// Check all three again in backwards order, to ensure we can go backwards
if (obj["c"].get<uint64_t>().first != 3) { cerr << "Expected uint64_t(obj[\"c\"]) to be 3, was " << obj["c"].first << endl; return false; }
if (obj["c/d"].get<uint64_t>().first != 3) { cerr << "Expected uint64_t(obj[\"c\"]) to be 3, was " << obj["c"].first << endl; return false; }
if (obj["b"].get<uint64_t>().first != 2) { cerr << "Expected uint64_t(obj[\"b\"]) to be 2, was " << obj["b"].first << endl; return false; }
if (obj["a"].get<uint64_t>().first != 1) { cerr << "Expected uint64_t(obj[\"a\"]) to be 1, was " << obj["a"].first << endl; return false; }

View File

@ -35,7 +35,7 @@ const padded_string TEST_JSON = R"(
bool json_pointer_success_test(const char *json_pointer, std::string_view expected_value) {
std::cout << "Running successful JSON pointer test '" << json_pointer << "' ..." << std::endl;
dom::parser parser;
auto [value, error] = parser.parse(TEST_JSON)[json_pointer].get<std::string_view>();
auto [value, error] = parser.parse(TEST_JSON).at(json_pointer).get<std::string_view>();
if (error) { std::cerr << "Unexpected Error: " << error << std::endl; return false; }
ASSERT(value == expected_value);
return true;
@ -44,7 +44,7 @@ bool json_pointer_success_test(const char *json_pointer, std::string_view expect
bool json_pointer_success_test(const char *json_pointer) {
std::cout << "Running successful JSON pointer test '" << json_pointer << "' ..." << std::endl;
dom::parser parser;
auto [value, error] = parser.parse(TEST_JSON)[json_pointer];
auto [value, error] = parser.parse(TEST_JSON).at(json_pointer);
if (error) { std::cerr << "Unexpected Error: " << error << std::endl; return false; }
return true;
}
@ -53,7 +53,7 @@ bool json_pointer_success_test(const char *json_pointer) {
bool json_pointer_failure_test(const char *json_pointer, error_code expected_failure_test) {
std::cout << "Running invalid JSON pointer test '" << json_pointer << "' ..." << std::endl;
dom::parser parser;
auto [value, error] = parser.parse(TEST_JSON)[json_pointer];
auto [value, error] = parser.parse(TEST_JSON).at(json_pointer);
ASSERT(error == expected_failure_test);
return true;
}