simdjson  3.3.0
Ridiculously Fast JSON
parsedjson_iterator-inl.h
1 #ifndef SIMDJSON_PARSEDJSON_ITERATOR_INL_H
2 #define SIMDJSON_PARSEDJSON_ITERATOR_INL_H
3 
4 #include "simdjson/dom/base.h"
5 #include "simdjson/dom/parsedjson_iterator.h"
6 #include "simdjson/internal/jsonformatutils.h"
7 
8 #include "simdjson/dom/parser-inl.h"
9 #include "simdjson/internal/tape_ref-inl.h"
10 
11 #include <cstring>
12 #include <iterator>
13 #include <limits>
14 #include <ostream>
15 
16 #ifndef SIMDJSON_DISABLE_DEPRECATED_API
17 
18 namespace simdjson {
19 
20 // VS2017 reports deprecated warnings when you define a deprecated class's methods.
21 SIMDJSON_PUSH_DISABLE_WARNINGS
22 SIMDJSON_DISABLE_DEPRECATED_WARNING
23 
24 // Because of template weirdness, the actual class definition is inline in the document class
25 simdjson_warn_unused bool dom::parser::Iterator::is_ok() const {
26  return location < tape_length;
27 }
28 
29 // useful for debugging purposes
30 size_t dom::parser::Iterator::get_tape_location() const {
31  return location;
32 }
33 
34 // useful for debugging purposes
35 size_t dom::parser::Iterator::get_tape_length() const {
36  return tape_length;
37 }
38 
39 // returns the current depth (start at 1 with 0 reserved for the fictitious root
40 // node)
41 size_t dom::parser::Iterator::get_depth() const {
42  return depth;
43 }
44 
45 // A scope is a series of nodes at the same depth, typically it is either an
46 // object ({) or an array ([). The root node has type 'r'.
47 uint8_t dom::parser::Iterator::get_scope_type() const {
48  return depth_index[depth].scope_type;
49 }
50 
51 bool dom::parser::Iterator::move_forward() {
52  if (location + 1 >= tape_length) {
53  return false; // we are at the end!
54  }
55 
56  if ((current_type == '[') || (current_type == '{')) {
57  // We are entering a new scope
58  depth++;
59  assert(depth < max_depth);
60  depth_index[depth].start_of_scope = location;
61  depth_index[depth].scope_type = current_type;
62  } else if ((current_type == ']') || (current_type == '}')) {
63  // Leaving a scope.
64  depth--;
65  } else if (is_number()) {
66  // these types use 2 locations on the tape, not just one.
67  location += 1;
68  }
69 
70  location += 1;
71  current_val = doc.tape[location];
72  current_type = uint8_t(current_val >> 56);
73  return true;
74 }
75 
76 void dom::parser::Iterator::move_to_value() {
77  // assume that we are on a key, so move by 1.
78  location += 1;
79  current_val = doc.tape[location];
80  current_type = uint8_t(current_val >> 56);
81 }
82 
83 bool dom::parser::Iterator::move_to_key(const char *key) {
84  if (down()) {
85  do {
86  const bool right_key = (strcmp(get_string(), key) == 0);
87  move_to_value();
88  if (right_key) {
89  return true;
90  }
91  } while (next());
92  up();
93  }
94  return false;
95 }
96 
97 bool dom::parser::Iterator::move_to_key_insensitive(
98  const char *key) {
99  if (down()) {
100  do {
101  const bool right_key = (simdjson_strcasecmp(get_string(), key) == 0);
102  move_to_value();
103  if (right_key) {
104  return true;
105  }
106  } while (next());
107  up();
108  }
109  return false;
110 }
111 
112 bool dom::parser::Iterator::move_to_key(const char *key,
113  uint32_t length) {
114  if (down()) {
115  do {
116  bool right_key = ((get_string_length() == length) &&
117  (memcmp(get_string(), key, length) == 0));
118  move_to_value();
119  if (right_key) {
120  return true;
121  }
122  } while (next());
123  up();
124  }
125  return false;
126 }
127 
128 bool dom::parser::Iterator::move_to_index(uint32_t index) {
129  if (down()) {
130  uint32_t i = 0;
131  for (; i < index; i++) {
132  if (!next()) {
133  break;
134  }
135  }
136  if (i == index) {
137  return true;
138  }
139  up();
140  }
141  return false;
142 }
143 
144 bool dom::parser::Iterator::prev() {
145  size_t target_location = location;
146  to_start_scope();
147  size_t npos = location;
148  if (target_location == npos) {
149  return false; // we were already at the start
150  }
151  size_t oldnpos;
152  // we have that npos < target_location here
153  do {
154  oldnpos = npos;
155  if ((current_type == '[') || (current_type == '{')) {
156  // we need to jump
157  npos = uint32_t(current_val);
158  } else {
159  npos = npos + ((current_type == 'd' || current_type == 'l') ? 2 : 1);
160  }
161  } while (npos < target_location);
162  location = oldnpos;
163  current_val = doc.tape[location];
164  current_type = uint8_t(current_val >> 56);
165  return true;
166 }
167 
168 bool dom::parser::Iterator::up() {
169  if (depth == 1) {
170  return false; // don't allow moving back to root
171  }
172  to_start_scope();
173  // next we just move to the previous value
174  depth--;
175  location -= 1;
176  current_val = doc.tape[location];
177  current_type = uint8_t(current_val >> 56);
178  return true;
179 }
180 
181 bool dom::parser::Iterator::down() {
182  if (location + 1 >= tape_length) {
183  return false;
184  }
185  if ((current_type == '[') || (current_type == '{')) {
186  size_t npos = uint32_t(current_val);
187  if (npos == location + 2) {
188  return false; // we have an empty scope
189  }
190  depth++;
191  assert(depth < max_depth);
192  location = location + 1;
193  depth_index[depth].start_of_scope = location;
194  depth_index[depth].scope_type = current_type;
195  current_val = doc.tape[location];
196  current_type = uint8_t(current_val >> 56);
197  return true;
198  }
199  return false;
200 }
201 
202 void dom::parser::Iterator::to_start_scope() {
203  location = depth_index[depth].start_of_scope;
204  current_val = doc.tape[location];
205  current_type = uint8_t(current_val >> 56);
206 }
207 
208 inline void dom::parser::Iterator::rewind() {
209  while (up())
210  ;
211 }
212 
213 
214 bool dom::parser::Iterator::next() {
215  size_t npos;
216  if ((current_type == '[') || (current_type == '{')) {
217  // we need to jump
218  npos = uint32_t(current_val);
219  } else {
220  npos = location + (is_number() ? 2 : 1);
221  }
222  uint64_t next_val = doc.tape[npos];
223  uint8_t next_type = uint8_t(next_val >> 56);
224  if ((next_type == ']') || (next_type == '}')) {
225  return false; // we reached the end of the scope
226  }
227  location = npos;
228  current_val = next_val;
229  current_type = next_type;
230  return true;
231 }
232 dom::parser::Iterator::Iterator(const dom::parser &pj) noexcept(false)
233  : doc(pj.doc)
234 {
235 #if SIMDJSON_EXCEPTIONS
236  if (!pj.valid) { throw simdjson_error(pj.error); }
237 #else
238  if (!pj.valid) { return; } // abort() usage is forbidden in the library
239 #endif
240 
241  max_depth = pj.max_depth();
242  depth_index = new scopeindex_t[max_depth + 1];
243  depth_index[0].start_of_scope = location;
244  current_val = doc.tape[location++];
245  current_type = uint8_t(current_val >> 56);
246  depth_index[0].scope_type = current_type;
247  tape_length = size_t(current_val & internal::JSON_VALUE_MASK);
248  if (location < tape_length) {
249  // If we make it here, then depth_capacity must >=2, but the compiler
250  // may not know this.
251  current_val = doc.tape[location];
252  current_type = uint8_t(current_val >> 56);
253  depth++;
254  assert(depth < max_depth);
255  depth_index[depth].start_of_scope = location;
256  depth_index[depth].scope_type = current_type;
257  }
258 }
259 dom::parser::Iterator::Iterator(
260  const dom::parser::Iterator &o) noexcept
261  : doc(o.doc),
262  max_depth(o.depth),
263  depth(o.depth),
264  location(o.location),
265  tape_length(o.tape_length),
266  current_type(o.current_type),
267  current_val(o.current_val)
268 {
269  depth_index = new scopeindex_t[max_depth+1];
270  std::memcpy(depth_index, o.depth_index, (depth + 1) * sizeof(depth_index[0]));
271 }
272 
273 dom::parser::Iterator::~Iterator() noexcept {
274  if (depth_index) { delete[] depth_index; }
275 }
276 
277 bool dom::parser::Iterator::print(std::ostream &os, bool escape_strings) const {
278  if (!is_ok()) {
279  return false;
280  }
281  switch (current_type) {
282  case '"': // we have a string
283  os << '"';
284  if (escape_strings) {
285  os << internal::escape_json_string(std::string_view(get_string(), get_string_length()));
286  } else {
287  // was: os << get_string();, but given that we can include null chars, we
288  // have to do something crazier:
289  std::copy(get_string(), get_string() + get_string_length(), std::ostream_iterator<char>(os));
290  }
291  os << '"';
292  break;
293  case 'l': // we have a long int
294  os << get_integer();
295  break;
296  case 'u':
297  os << get_unsigned_integer();
298  break;
299  case 'd':
300  os << get_double();
301  break;
302  case 'n': // we have a null
303  os << "null";
304  break;
305  case 't': // we have a true
306  os << "true";
307  break;
308  case 'f': // we have a false
309  os << "false";
310  break;
311  case '{': // we have an object
312  case '}': // we end an object
313  case '[': // we start an array
314  case ']': // we end an array
315  os << char(current_type);
316  break;
317  default:
318  return false;
319  }
320  return true;
321 }
322 
323 bool dom::parser::Iterator::move_to(const char *pointer,
324  uint32_t length) {
325  char *new_pointer = nullptr;
326  if (pointer[0] == '#') {
327  // Converting fragment representation to string representation
328  new_pointer = new char[length];
329  uint32_t new_length = 0;
330  for (uint32_t i = 1; i < length; i++) {
331  if (pointer[i] == '%' && pointer[i + 1] == 'x') {
332 #if __cpp_exceptions
333  try {
334 #endif
335  int fragment =
336  std::stoi(std::string(&pointer[i + 2], 2), nullptr, 16);
337  if (fragment == '\\' || fragment == '"' || (fragment <= 0x1F)) {
338  // escaping the character
339  new_pointer[new_length] = '\\';
340  new_length++;
341  }
342  new_pointer[new_length] = char(fragment);
343  i += 3;
344 #if __cpp_exceptions
345  } catch (std::invalid_argument &) {
346  delete[] new_pointer;
347  return false; // the fragment is invalid
348  }
349 #endif
350  } else {
351  new_pointer[new_length] = pointer[i];
352  }
353  new_length++;
354  }
355  length = new_length;
356  pointer = new_pointer;
357  }
358 
359  // saving the current state
360  size_t depth_s = depth;
361  size_t location_s = location;
362  uint8_t current_type_s = current_type;
363  uint64_t current_val_s = current_val;
364 
365  rewind(); // The json pointer is used from the root of the document.
366 
367  bool found = relative_move_to(pointer, length);
368  delete[] new_pointer;
369 
370  if (!found) {
371  // since the pointer has found nothing, we get back to the original
372  // position.
373  depth = depth_s;
374  location = location_s;
375  current_type = current_type_s;
376  current_val = current_val_s;
377  }
378 
379  return found;
380 }
381 
382 inline bool dom::parser::Iterator::move_to(const std::string &pointer) {
383  return move_to(pointer.c_str(), uint32_t(pointer.length()));
384 }
385 
386 inline int64_t dom::parser::Iterator::get_integer() const {
387  if (location + 1 >= tape_length) {
388  return 0; // default value in case of error
389  }
390  return static_cast<int64_t>(doc.tape[location + 1]);
391 }
392 
393 inline uint64_t dom::parser::Iterator::get_unsigned_integer() const {
394  if (location + 1 >= tape_length) {
395  return 0; // default value in case of error
396  }
397  return doc.tape[location + 1];
398 }
399 
400 inline const char * dom::parser::Iterator::get_string() const {
401  return reinterpret_cast<const char *>(
402  doc.string_buf.get() + (current_val & internal::JSON_VALUE_MASK) + sizeof(uint32_t));
403 }
404 
405 inline uint32_t dom::parser::Iterator::get_string_length() const {
406  uint32_t answer;
407  std::memcpy(&answer,
408  reinterpret_cast<const char *>(doc.string_buf.get() +
409  (current_val & internal::JSON_VALUE_MASK)),
410  sizeof(uint32_t));
411  return answer;
412 }
413 
414 inline double dom::parser::Iterator::get_double() const {
415  if (location + 1 >= tape_length) {
416  return std::numeric_limits<double>::quiet_NaN(); // default value in
417  // case of error
418  }
419  double answer;
420  std::memcpy(&answer, &doc.tape[location + 1], sizeof(answer));
421  return answer;
422 }
423 
424 bool dom::parser::Iterator::relative_move_to(const char *pointer,
425  uint32_t length) {
426  if (length == 0) {
427  // returns the whole document
428  return true;
429  }
430 
431  if (pointer[0] != '/') {
432  // '/' must be the first character
433  return false;
434  }
435 
436  // finding the key in an object or the index in an array
437  std::string key_or_index;
438  uint32_t offset = 1;
439 
440  // checking for the "-" case
441  if (is_array() && pointer[1] == '-') {
442  if (length != 2) {
443  // the pointer must be exactly "/-"
444  // there can't be anything more after '-' as an index
445  return false;
446  }
447  key_or_index = '-';
448  offset = length; // will skip the loop coming right after
449  }
450 
451  // We either transform the first reference token to a valid json key
452  // or we make sure it is a valid index in an array.
453  for (; offset < length; offset++) {
454  if (pointer[offset] == '/') {
455  // beginning of the next key or index
456  break;
457  }
458  if (is_array() && (pointer[offset] < '0' || pointer[offset] > '9')) {
459  // the index of an array must be an integer
460  // we also make sure std::stoi won't discard whitespaces later
461  return false;
462  }
463  if (pointer[offset] == '~') {
464  // "~1" represents "/"
465  if (pointer[offset + 1] == '1') {
466  key_or_index += '/';
467  offset++;
468  continue;
469  }
470  // "~0" represents "~"
471  if (pointer[offset + 1] == '0') {
472  key_or_index += '~';
473  offset++;
474  continue;
475  }
476  }
477  if (pointer[offset] == '\\') {
478  if (pointer[offset + 1] == '\\' || pointer[offset + 1] == '"' ||
479  (pointer[offset + 1] <= 0x1F)) {
480  key_or_index += pointer[offset + 1];
481  offset++;
482  continue;
483  }
484  return false; // invalid escaped character
485  }
486  if (pointer[offset] == '\"') {
487  // unescaped quote character. this is an invalid case.
488  // lets do nothing and assume most pointers will be valid.
489  // it won't find any corresponding json key anyway.
490  // return false;
491  }
492  key_or_index += pointer[offset];
493  }
494 
495  bool found = false;
496  if (is_object()) {
497  if (move_to_key(key_or_index.c_str(), uint32_t(key_or_index.length()))) {
498  found = relative_move_to(pointer + offset, length - offset);
499  }
500  } else if (is_array()) {
501  if (key_or_index == "-") { // handling "-" case first
502  if (down()) {
503  while (next())
504  ; // moving to the end of the array
505  // moving to the nonexistent value right after...
506  size_t npos;
507  if ((current_type == '[') || (current_type == '{')) {
508  // we need to jump
509  npos = uint32_t(current_val);
510  } else {
511  npos =
512  location + ((current_type == 'd' || current_type == 'l') ? 2 : 1);
513  }
514  location = npos;
515  current_val = doc.tape[npos];
516  current_type = uint8_t(current_val >> 56);
517  return true; // how could it fail ?
518  }
519  } else { // regular numeric index
520  // The index can't have a leading '0'
521  if (key_or_index[0] == '0' && key_or_index.length() > 1) {
522  return false;
523  }
524  // it cannot be empty
525  if (key_or_index.length() == 0) {
526  return false;
527  }
528  // we already checked the index contains only valid digits
529  uint32_t index = std::stoi(key_or_index);
530  if (move_to_index(index)) {
531  found = relative_move_to(pointer + offset, length - offset);
532  }
533  }
534  }
535 
536  return found;
537 }
538 
539 SIMDJSON_POP_DISABLE_WARNINGS
540 } // namespace simdjson
541 
542 #endif // SIMDJSON_DISABLE_DEPRECATED_API
543 
544 
545 #endif // SIMDJSON_PARSEDJSON_ITERATOR_INL_H
The top level simdjson namespace, containing everything the library provides.
Definition: base.h:8