mirror of
https://github.com/simdjson/simdjson
synced 2026-06-08 17:27:07 +00:00
484 lines
16 KiB
Plaintext
484 lines
16 KiB
Plaintext
#ifndef SIMDJSON_OBJECT_INL_H
|
|
#define SIMDJSON_OBJECT_INL_H
|
|
|
|
#include "simdjson/dom/base.h"
|
|
#include "simdjson/dom/document.h"
|
|
#include "simdjson/dom/object.h"
|
|
|
|
#include "simdjson/dom/element-inl.h"
|
|
#include "simdjson/error-inl.h"
|
|
#include "simdjson/jsonpathutil.h"
|
|
|
|
#include <cstring>
|
|
|
|
namespace simdjson {
|
|
|
|
//
|
|
// simdjson_result<dom::object> inline implementation
|
|
//
|
|
simdjson_inline simdjson_result<dom::object>::simdjson_result() noexcept
|
|
: internal::simdjson_result_base<dom::object>() {}
|
|
simdjson_inline
|
|
simdjson_result<dom::object>::simdjson_result(dom::object value) noexcept
|
|
: internal::simdjson_result_base<dom::object>(
|
|
std::forward<dom::object>(value)) {}
|
|
simdjson_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[](std::string_view key) const noexcept {
|
|
if (error()) {
|
|
return error();
|
|
}
|
|
return first[key];
|
|
}
|
|
inline simdjson_result<dom::element>
|
|
simdjson_result<dom::object>::operator[](const char *key) const noexcept {
|
|
if (error()) {
|
|
return error();
|
|
}
|
|
return first[key];
|
|
}
|
|
inline simdjson_result<dom::element> simdjson_result<dom::object>::at_pointer(
|
|
std::string_view json_pointer) const noexcept {
|
|
if (error()) {
|
|
return error();
|
|
}
|
|
return first.at_pointer(json_pointer);
|
|
}
|
|
inline simdjson_result<std::vector<dom::element>>
|
|
simdjson_result<dom::object>::at_path_with_wildcard(
|
|
std::string_view json_path) const noexcept {
|
|
if (error()) {
|
|
return error();
|
|
}
|
|
return first.at_path_with_wildcard(json_path);
|
|
}
|
|
inline simdjson_result<dom::element> simdjson_result<dom::object>::at_path(
|
|
std::string_view json_path) const noexcept {
|
|
auto json_pointer = json_path_to_pointer_conversion(json_path);
|
|
if (json_pointer == "-1") {
|
|
return INVALID_JSON_POINTER;
|
|
}
|
|
return at_pointer(json_pointer);
|
|
}
|
|
inline simdjson_result<dom::element>
|
|
simdjson_result<dom::object>::at_key(std::string_view key) const noexcept {
|
|
if (error()) {
|
|
return error();
|
|
}
|
|
return first.at_key(key);
|
|
}
|
|
inline simdjson_result<std::vector<dom::element>>
|
|
simdjson_result<dom::object>::get_values() const noexcept {
|
|
if (error()) {
|
|
return error();
|
|
}
|
|
return first.get_values();
|
|
}
|
|
inline simdjson_result<dom::element>
|
|
simdjson_result<dom::object>::at_key_case_insensitive(
|
|
std::string_view key) const noexcept {
|
|
if (error()) {
|
|
return error();
|
|
}
|
|
return first.at_key_case_insensitive(key);
|
|
}
|
|
|
|
#if SIMDJSON_EXCEPTIONS
|
|
|
|
inline dom::object::iterator simdjson_result<dom::object>::begin() const
|
|
noexcept(false) {
|
|
if (error()) {
|
|
throw simdjson_error(error());
|
|
}
|
|
return first.begin();
|
|
}
|
|
inline dom::object::iterator simdjson_result<dom::object>::end() const
|
|
noexcept(false) {
|
|
if (error()) {
|
|
throw simdjson_error(error());
|
|
}
|
|
return first.end();
|
|
}
|
|
inline size_t simdjson_result<dom::object>::size() const noexcept(false) {
|
|
if (error()) {
|
|
throw simdjson_error(error());
|
|
}
|
|
return first.size();
|
|
}
|
|
|
|
#endif // SIMDJSON_EXCEPTIONS
|
|
|
|
namespace dom {
|
|
|
|
//
|
|
// object inline implementation
|
|
//
|
|
simdjson_inline object::object() noexcept : tape{} {}
|
|
simdjson_inline object::object(const internal::tape_ref &_tape) noexcept
|
|
: tape{_tape} {}
|
|
inline object::iterator object::begin() const noexcept {
|
|
SIMDJSON_DEVELOPMENT_ASSERT(
|
|
tape.usable()); // https://github.com/simdjson/simdjson/issues/1914
|
|
return internal::tape_ref(tape.doc, tape.json_index + 1);
|
|
}
|
|
inline object::iterator object::end() const noexcept {
|
|
SIMDJSON_DEVELOPMENT_ASSERT(
|
|
tape.usable()); // https://github.com/simdjson/simdjson/issues/1914
|
|
return internal::tape_ref(tape.doc, tape.after_element() - 1);
|
|
}
|
|
inline size_t object::size() const noexcept {
|
|
SIMDJSON_DEVELOPMENT_ASSERT(
|
|
tape.usable()); // https://github.com/simdjson/simdjson/issues/1914
|
|
return tape.scope_count();
|
|
}
|
|
|
|
inline simdjson_result<element>
|
|
object::operator[](std::string_view key) const noexcept {
|
|
return at_key(key);
|
|
}
|
|
inline simdjson_result<element>
|
|
object::operator[](const char *key) const noexcept {
|
|
return at_key(key);
|
|
}
|
|
inline simdjson_result<element>
|
|
object::at_pointer(std::string_view json_pointer) const noexcept {
|
|
SIMDJSON_DEVELOPMENT_ASSERT(
|
|
tape.usable()); // https://github.com/simdjson/simdjson/issues/1914
|
|
if (json_pointer
|
|
.empty()) { // an empty string means that we return the current node
|
|
return element(this->tape); // copy the current node
|
|
} else if (json_pointer[0] != '/') { // otherwise there is an error
|
|
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<element> 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 = at_key(unescaped);
|
|
} else {
|
|
child = at_key(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;
|
|
}
|
|
inline simdjson_result<std::vector<element>>
|
|
object::at_path_with_wildcard(std::string_view json_path) const noexcept {
|
|
SIMDJSON_DEVELOPMENT_ASSERT(
|
|
tape.usable()); // https://github.com/simdjson/simdjson/issues/1914
|
|
if (json_path
|
|
.empty()) { // an empty string means that we return the current node
|
|
// TODO: revisit handling this - return element(this->tape); // copy the
|
|
// current node
|
|
return {};
|
|
}
|
|
|
|
// TODO: Do some additional json_path validation here
|
|
// else if(json_pointer[0] != '/') { // otherwise there is an error
|
|
// return INVALID_JSON_POINTER;
|
|
// }
|
|
|
|
|
|
if (json_path.length() == 4) {
|
|
std::string_view match = "$[*]";
|
|
if (memcmp(json_path.data(), match.data(), 4) == 0) {
|
|
return get_values();
|
|
}
|
|
}
|
|
|
|
if (json_path.length() == 3) {
|
|
std::string_view match = "$.*";
|
|
if (memcmp(json_path.data(), match.data(), 3) == 0) {
|
|
return get_values();
|
|
}
|
|
}
|
|
|
|
if (json_path.find("*") != std::string::npos) {
|
|
|
|
// TODO: I am assuming is not valid to have a nested check on the result of
|
|
// a wildcard so I'm not checking for wildcard here, but just keys This
|
|
// assumption is probably wrong and I will need to verify
|
|
|
|
// JSONPath starts with $
|
|
if (!json_path.empty() &&
|
|
json_path.front() ==
|
|
'$') { // TODO: Must all Json path start with a root element ($)?
|
|
// for json_path - $.example_key.* or $["example_key"].*
|
|
json_path = json_path.substr(1); // .example_key.* or ["example_key"].*
|
|
if (json_path[0] == '.' || json_path[0] == '[') {
|
|
json_path = json_path.substr(1); // example_key.* or "example_key"].*
|
|
|
|
// recursive e.g example_key.* or example_key[*]
|
|
size_t second_dot = json_path.find('.');
|
|
if (second_dot != std::string_view::npos && (json_path[0] != '"' && json_path[0] != '\'')) // example_key.*
|
|
{
|
|
std::string_view key = json_path.substr(0, second_dot); // example_key or *
|
|
simdjson_result<std::vector<element>> child = {};
|
|
|
|
if (key == "*") {
|
|
child = get_values();
|
|
} else {
|
|
child = std::vector<element>{at_pointer("/" + std::string(key)).value()};
|
|
}
|
|
|
|
json_path = json_path.substr(second_dot); // .example_key or .*
|
|
std::vector<element> child_value = child.value();
|
|
std::vector<element>::iterator iterator = child_value.begin();
|
|
|
|
std::vector<element> result;
|
|
|
|
for (; iterator != child_value.end(); ++iterator) {
|
|
auto child_result =
|
|
iterator->at_path_with_wildcard("$" + std::string(json_path)).value();
|
|
result.insert(result.end(), child_result.begin(),
|
|
child_result.end());
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// recursive e.g "example_key"].* or "example_key"][*]
|
|
size_t close_bracket = json_path.find(']');
|
|
if (close_bracket != std::string_view::npos && (json_path[0] == '"' || json_path[0] == '\'')) {
|
|
auto key = json_path.substr(1, close_bracket - 2); // example_key or *
|
|
|
|
simdjson_result<std::vector<element>> child = {};
|
|
if (key == "*") {
|
|
child = get_values();
|
|
} else {
|
|
child = std::vector<element>{at_pointer("/" + std::string(key)).value()};
|
|
}
|
|
json_path = json_path.substr(close_bracket + 1); // .* or [*]
|
|
std::vector<element> child_value = child.value();
|
|
std::vector<element>::iterator iterator = child_value.begin();
|
|
|
|
std::vector<element> result;
|
|
|
|
for (; iterator != child_value.end(); ++iterator) {
|
|
auto child_result =
|
|
iterator->at_path_with_wildcard("$" + std::string(json_path)).value();
|
|
result.insert(result.end(), child_result.begin(),
|
|
child_result.end());
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// TODO: Handle here
|
|
return {};
|
|
}
|
|
|
|
// TODO: Handle only key here
|
|
} else {
|
|
// TODO: Handle JSON starting without $
|
|
return INVALID_JSON_POINTER; // TODO: Create exception for invalid
|
|
// JSON_PATH
|
|
}
|
|
} else {
|
|
return std::vector<element>{this->at_path(json_path).value()};
|
|
}
|
|
if (json_path.empty() || (json_path[0] != '.' && json_path[0] != '[')) {
|
|
return {}; // TODO: Revisit
|
|
}
|
|
|
|
return {}; // TODO: Revisit this also
|
|
}
|
|
inline simdjson_result<element>
|
|
object::at_path(std::string_view json_path) const noexcept {
|
|
auto json_pointer = json_path_to_pointer_conversion(json_path);
|
|
if (json_pointer == "-1") {
|
|
return INVALID_JSON_POINTER;
|
|
}
|
|
return at_pointer(json_pointer);
|
|
}
|
|
|
|
inline simdjson_result<element>
|
|
object::at_key(std::string_view key) const noexcept {
|
|
iterator end_field = end();
|
|
for (iterator field = begin(); field != end_field; ++field) {
|
|
if (field.key_equals(key)) {
|
|
return field.value();
|
|
}
|
|
}
|
|
return NO_SUCH_FIELD;
|
|
}
|
|
|
|
inline simdjson_result<std::vector<element>>
|
|
object::get_values() const noexcept {
|
|
iterator end_field = end();
|
|
|
|
std::vector<element> result =
|
|
{}; // TODO: Do some allocations here for optimization?
|
|
for (iterator field = begin(); field != end_field; ++field) {
|
|
result.push_back(field.value());
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// In case you wonder why we need this, please see
|
|
// https://github.com/simdjson/simdjson/issues/323
|
|
// People do seek keys in a case-insensitive manner.
|
|
inline simdjson_result<element>
|
|
object::at_key_case_insensitive(std::string_view key) const noexcept {
|
|
iterator end_field = end();
|
|
for (iterator field = begin(); field != end_field; ++field) {
|
|
if (field.key_equals_case_insensitive(key)) {
|
|
return field.value();
|
|
}
|
|
}
|
|
return NO_SUCH_FIELD;
|
|
}
|
|
|
|
inline object::operator element() const noexcept { return element(tape); }
|
|
|
|
//
|
|
// object::iterator inline implementation
|
|
//
|
|
simdjson_inline
|
|
object::iterator::iterator(const internal::tape_ref &_tape) noexcept
|
|
: tape{_tape} {}
|
|
inline const key_value_pair object::iterator::operator*() const noexcept {
|
|
return key_value_pair(key(), value());
|
|
}
|
|
inline bool
|
|
object::iterator::operator!=(const object::iterator &other) const noexcept {
|
|
return tape.json_index != other.tape.json_index;
|
|
}
|
|
inline bool
|
|
object::iterator::operator==(const object::iterator &other) const noexcept {
|
|
return tape.json_index == other.tape.json_index;
|
|
}
|
|
inline bool
|
|
object::iterator::operator<(const object::iterator &other) const noexcept {
|
|
return tape.json_index < other.tape.json_index;
|
|
}
|
|
inline bool
|
|
object::iterator::operator<=(const object::iterator &other) const noexcept {
|
|
return tape.json_index <= other.tape.json_index;
|
|
}
|
|
inline bool
|
|
object::iterator::operator>=(const object::iterator &other) const noexcept {
|
|
return tape.json_index >= other.tape.json_index;
|
|
}
|
|
inline bool
|
|
object::iterator::operator>(const object::iterator &other) const noexcept {
|
|
return tape.json_index > other.tape.json_index;
|
|
}
|
|
inline object::iterator &object::iterator::operator++() noexcept {
|
|
tape.json_index++;
|
|
tape.json_index = tape.after_element();
|
|
return *this;
|
|
}
|
|
inline object::iterator object::iterator::operator++(int) noexcept {
|
|
object::iterator out = *this;
|
|
++*this;
|
|
return out;
|
|
}
|
|
inline std::string_view object::iterator::key() const noexcept {
|
|
return tape.get_string_view();
|
|
}
|
|
inline uint32_t object::iterator::key_length() const noexcept {
|
|
return tape.get_string_length();
|
|
}
|
|
inline const char *object::iterator::key_c_str() const noexcept {
|
|
return reinterpret_cast<const char *>(
|
|
&tape.doc->string_buf[size_t(tape.tape_value()) + sizeof(uint32_t)]);
|
|
}
|
|
inline element object::iterator::value() const noexcept {
|
|
return element(internal::tape_ref(tape.doc, tape.json_index + 1));
|
|
}
|
|
|
|
/**
|
|
* Design notes:
|
|
* Instead of constructing a string_view and then comparing it with a
|
|
* user-provided strings, it is probably more performant to have dedicated
|
|
* functions taking as a parameter the string we want to compare against
|
|
* and return true when they are equal. That avoids the creation of a
|
|
* temporary std::string_view. Though it is possible for the compiler to avoid
|
|
* entirely any overhead due to string_view, relying too much on compiler
|
|
* magic is problematic: compiler magic sometimes fail, and then what do you
|
|
* do? Also, enticing users to rely on high-performance function is probably
|
|
* better on the long run.
|
|
*/
|
|
|
|
inline bool object::iterator::key_equals(std::string_view o) const noexcept {
|
|
// We use the fact that the key length can be computed quickly
|
|
// without access to the string buffer.
|
|
const uint32_t len = key_length();
|
|
if (o.size() == len) {
|
|
// We avoid construction of a temporary string_view instance.
|
|
return (memcmp(o.data(), key_c_str(), len) == 0);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
inline bool object::iterator::key_equals_case_insensitive(
|
|
std::string_view o) const noexcept {
|
|
// We use the fact that the key length can be computed quickly
|
|
// without access to the string buffer.
|
|
const uint32_t len = key_length();
|
|
if (o.size() == len) {
|
|
// See For case-insensitive string comparisons, avoid char-by-char
|
|
// functions
|
|
// https://lemire.me/blog/2020/04/30/for-case-insensitive-string-comparisons-avoid-char-by-char-functions/
|
|
// Note that it might be worth rolling our own strncasecmp function, with
|
|
// vectorization.
|
|
return (simdjson_strncasecmp(o.data(), key_c_str(), len) == 0);
|
|
}
|
|
return false;
|
|
}
|
|
//
|
|
// key_value_pair inline implementation
|
|
//
|
|
inline key_value_pair::key_value_pair(std::string_view _key,
|
|
element _value) noexcept
|
|
: key(_key), value(_value) {}
|
|
|
|
} // namespace dom
|
|
|
|
} // namespace simdjson
|
|
|
|
#if defined(__cpp_lib_ranges)
|
|
static_assert(std::ranges::view<simdjson::dom::object>);
|
|
static_assert(std::ranges::sized_range<simdjson::dom::object>);
|
|
#if SIMDJSON_EXCEPTIONS
|
|
static_assert(
|
|
std::ranges::view<simdjson::simdjson_result<simdjson::dom::object>>);
|
|
static_assert(
|
|
std::ranges::sized_range<simdjson::simdjson_result<simdjson::dom::object>>);
|
|
#endif // SIMDJSON_EXCEPTIONS
|
|
#endif // defined(__cpp_lib_ranges)
|
|
|
|
#endif // SIMDJSON_OBJECT_INL_H
|