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