1 | #ifndef SIMDJSON_INLINE_OBJECT_H |
2 | #define SIMDJSON_INLINE_OBJECT_H |
3 | |
4 | #include "simdjson/dom/element.h" |
5 | #include "simdjson/dom/object.h" |
6 | #include "simdjson/portability.h" |
7 | #include <cstring> |
8 | #include <string> |
9 | |
10 | namespace simdjson { |
11 | |
12 | // |
13 | // simdjson_result<dom::object> inline implementation |
14 | // |
15 | simdjson_inline simdjson_result<dom::object>::simdjson_result() noexcept |
16 | : internal::simdjson_result_base<dom::object>() {} |
17 | simdjson_inline simdjson_result<dom::object>::simdjson_result(dom::object value) noexcept |
18 | : internal::simdjson_result_base<dom::object>(std::forward<dom::object>(t&: value)) {} |
19 | simdjson_inline simdjson_result<dom::object>::simdjson_result(error_code error) noexcept |
20 | : internal::simdjson_result_base<dom::object>(error) {} |
21 | |
22 | inline simdjson_result<dom::element> simdjson_result<dom::object>::operator[](std::string_view key) const noexcept { |
23 | if (error()) { return error(); } |
24 | return first[key]; |
25 | } |
26 | inline simdjson_result<dom::element> simdjson_result<dom::object>::operator[](const char *key) const noexcept { |
27 | if (error()) { return error(); } |
28 | return first[key]; |
29 | } |
30 | inline simdjson_result<dom::element> simdjson_result<dom::object>::at_pointer(std::string_view json_pointer) const noexcept { |
31 | if (error()) { return error(); } |
32 | return first.at_pointer(json_pointer); |
33 | } |
34 | inline simdjson_result<dom::element> simdjson_result<dom::object>::at_key(std::string_view key) const noexcept { |
35 | if (error()) { return error(); } |
36 | return first.at_key(key); |
37 | } |
38 | inline simdjson_result<dom::element> simdjson_result<dom::object>::at_key_case_insensitive(std::string_view key) const noexcept { |
39 | if (error()) { return error(); } |
40 | return first.at_key_case_insensitive(key); |
41 | } |
42 | |
43 | #if SIMDJSON_EXCEPTIONS |
44 | |
45 | inline dom::object::iterator simdjson_result<dom::object>::begin() const noexcept(false) { |
46 | if (error()) { throw simdjson_error(error()); } |
47 | return first.begin(); |
48 | } |
49 | inline dom::object::iterator simdjson_result<dom::object>::end() const noexcept(false) { |
50 | if (error()) { throw simdjson_error(error()); } |
51 | return first.end(); |
52 | } |
53 | inline size_t simdjson_result<dom::object>::size() const noexcept(false) { |
54 | if (error()) { throw simdjson_error(error()); } |
55 | return first.size(); |
56 | } |
57 | |
58 | #endif // SIMDJSON_EXCEPTIONS |
59 | |
60 | namespace dom { |
61 | |
62 | // |
63 | // object inline implementation |
64 | // |
65 | simdjson_inline object::object() noexcept : tape{} {} |
66 | simdjson_inline object::object(const internal::tape_ref &_tape) noexcept : tape{_tape} { } |
67 | inline object::iterator object::begin() const noexcept { |
68 | SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 |
69 | return internal::tape_ref(tape.doc, tape.json_index + 1); |
70 | } |
71 | inline object::iterator object::end() const noexcept { |
72 | SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 |
73 | return internal::tape_ref(tape.doc, tape.after_element() - 1); |
74 | } |
75 | inline size_t object::size() const noexcept { |
76 | SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 |
77 | return tape.scope_count(); |
78 | } |
79 | |
80 | inline simdjson_result<element> object::operator[](std::string_view key) const noexcept { |
81 | return at_key(key); |
82 | } |
83 | inline simdjson_result<element> object::operator[](const char *key) const noexcept { |
84 | return at_key(key); |
85 | } |
86 | inline simdjson_result<element> object::at_pointer(std::string_view json_pointer) const noexcept { |
87 | SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 |
88 | if(json_pointer.empty()) { // an empty string means that we return the current node |
89 | return element(this->tape); // copy the current node |
90 | } else if(json_pointer[0] != '/') { // otherwise there is an error |
91 | return INVALID_JSON_POINTER; |
92 | } |
93 | json_pointer = json_pointer.substr(pos: 1); |
94 | size_t slash = json_pointer.find(c: '/'); |
95 | std::string_view key = json_pointer.substr(pos: 0, n: slash); |
96 | // Grab the child with the given key |
97 | simdjson_result<element> child; |
98 | |
99 | // If there is an escape character in the key, unescape it and then get the child. |
100 | size_t escape = key.find(c: '~'); |
101 | if (escape != std::string_view::npos) { |
102 | // Unescape the key |
103 | std::string unescaped(key); |
104 | do { |
105 | switch (unescaped[escape+1]) { |
106 | case '0': |
107 | unescaped.replace(pos: escape, n1: 2, s: "~" ); |
108 | break; |
109 | case '1': |
110 | unescaped.replace(pos: escape, n1: 2, s: "/" ); |
111 | break; |
112 | default: |
113 | return INVALID_JSON_POINTER; // "Unexpected ~ escape character in JSON pointer"); |
114 | } |
115 | escape = unescaped.find(c: '~', pos: escape+1); |
116 | } while (escape != std::string::npos); |
117 | child = at_key(key: unescaped); |
118 | } else { |
119 | child = at_key(key); |
120 | } |
121 | if(child.error()) { |
122 | return child; // we do not continue if there was an error |
123 | } |
124 | // If there is a /, we have to recurse and look up more of the path |
125 | if (slash != std::string_view::npos) { |
126 | child = child.at_pointer(json_pointer: json_pointer.substr(pos: slash)); |
127 | } |
128 | return child; |
129 | } |
130 | |
131 | inline simdjson_result<element> object::at_key(std::string_view key) const noexcept { |
132 | iterator end_field = end(); |
133 | for (iterator field = begin(); field != end_field; ++field) { |
134 | if (field.key_equals(o: key)) { |
135 | return field.value(); |
136 | } |
137 | } |
138 | return NO_SUCH_FIELD; |
139 | } |
140 | // In case you wonder why we need this, please see |
141 | // https://github.com/simdjson/simdjson/issues/323 |
142 | // People do seek keys in a case-insensitive manner. |
143 | inline simdjson_result<element> object::at_key_case_insensitive(std::string_view key) const noexcept { |
144 | iterator end_field = end(); |
145 | for (iterator field = begin(); field != end_field; ++field) { |
146 | if (field.key_equals_case_insensitive(o: key)) { |
147 | return field.value(); |
148 | } |
149 | } |
150 | return NO_SUCH_FIELD; |
151 | } |
152 | |
153 | // |
154 | // object::iterator inline implementation |
155 | // |
156 | simdjson_inline object::iterator::iterator(const internal::tape_ref &_tape) noexcept : tape{_tape} { } |
157 | inline const key_value_pair object::iterator::operator*() const noexcept { |
158 | return key_value_pair(key(), value()); |
159 | } |
160 | inline bool object::iterator::operator!=(const object::iterator& other) const noexcept { |
161 | return tape.json_index != other.tape.json_index; |
162 | } |
163 | inline bool object::iterator::operator==(const object::iterator& other) const noexcept { |
164 | return tape.json_index == other.tape.json_index; |
165 | } |
166 | inline bool object::iterator::operator<(const object::iterator& other) const noexcept { |
167 | return tape.json_index < other.tape.json_index; |
168 | } |
169 | inline bool object::iterator::operator<=(const object::iterator& other) const noexcept { |
170 | return tape.json_index <= other.tape.json_index; |
171 | } |
172 | inline bool object::iterator::operator>=(const object::iterator& other) const noexcept { |
173 | return tape.json_index >= other.tape.json_index; |
174 | } |
175 | inline bool object::iterator::operator>(const object::iterator& other) const noexcept { |
176 | return tape.json_index > other.tape.json_index; |
177 | } |
178 | inline object::iterator& object::iterator::operator++() noexcept { |
179 | tape.json_index++; |
180 | tape.json_index = tape.after_element(); |
181 | return *this; |
182 | } |
183 | inline object::iterator object::iterator::operator++(int) noexcept { |
184 | object::iterator out = *this; |
185 | ++*this; |
186 | return out; |
187 | } |
188 | inline std::string_view object::iterator::key() const noexcept { |
189 | return tape.get_string_view(); |
190 | } |
191 | inline uint32_t object::iterator::key_length() const noexcept { |
192 | return tape.get_string_length(); |
193 | } |
194 | inline const char* object::iterator::key_c_str() const noexcept { |
195 | return reinterpret_cast<const char *>(&tape.doc->string_buf[size_t(tape.tape_value()) + sizeof(uint32_t)]); |
196 | } |
197 | inline element object::iterator::value() const noexcept { |
198 | return element(internal::tape_ref(tape.doc, tape.json_index + 1)); |
199 | } |
200 | |
201 | /** |
202 | * Design notes: |
203 | * Instead of constructing a string_view and then comparing it with a |
204 | * user-provided strings, it is probably more performant to have dedicated |
205 | * functions taking as a parameter the string we want to compare against |
206 | * and return true when they are equal. That avoids the creation of a temporary |
207 | * std::string_view. Though it is possible for the compiler to avoid entirely |
208 | * any overhead due to string_view, relying too much on compiler magic is |
209 | * problematic: compiler magic sometimes fail, and then what do you do? |
210 | * Also, enticing users to rely on high-performance function is probably better |
211 | * on the long run. |
212 | */ |
213 | |
214 | inline bool object::iterator::key_equals(std::string_view o) const noexcept { |
215 | // We use the fact that the key length can be computed quickly |
216 | // without access to the string buffer. |
217 | const uint32_t len = key_length(); |
218 | if(o.size() == len) { |
219 | // We avoid construction of a temporary string_view instance. |
220 | return (memcmp(s1: o.data(), s2: key_c_str(), n: len) == 0); |
221 | } |
222 | return false; |
223 | } |
224 | |
225 | inline bool object::iterator::key_equals_case_insensitive(std::string_view o) const noexcept { |
226 | // We use the fact that the key length can be computed quickly |
227 | // without access to the string buffer. |
228 | const uint32_t len = key_length(); |
229 | if(o.size() == len) { |
230 | // See For case-insensitive string comparisons, avoid char-by-char functions |
231 | // https://lemire.me/blog/2020/04/30/for-case-insensitive-string-comparisons-avoid-char-by-char-functions/ |
232 | // Note that it might be worth rolling our own strncasecmp function, with vectorization. |
233 | return (simdjson_strncasecmp(s1: o.data(), s2: key_c_str(), n: len) == 0); |
234 | } |
235 | return false; |
236 | } |
237 | // |
238 | // key_value_pair inline implementation |
239 | // |
240 | inline key_value_pair::key_value_pair(std::string_view _key, element _value) noexcept : |
241 | key(_key), value(_value) {} |
242 | |
243 | } // namespace dom |
244 | |
245 | } // namespace simdjson |
246 | |
247 | #if defined(__cpp_lib_ranges) |
248 | static_assert(std::ranges::view<simdjson::dom::object>); |
249 | static_assert(std::ranges::sized_range<simdjson::dom::object>); |
250 | #if SIMDJSON_EXCEPTIONS |
251 | static_assert(std::ranges::view<simdjson::simdjson_result<simdjson::dom::object>>); |
252 | static_assert(std::ranges::sized_range<simdjson::simdjson_result<simdjson::dom::object>>); |
253 | #endif // SIMDJSON_EXCEPTIONS |
254 | #endif // defined(__cpp_lib_ranges) |
255 | |
256 | #endif // SIMDJSON_INLINE_OBJECT_H |
257 | |