Coverage Report

Created: 2025-11-24 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/glaze/include/glaze/json/jmespath.hpp
Line
Count
Source
1
// Glaze Library
2
// For the license information refer to glaze.hpp
3
4
#pragma once
5
6
#include <algorithm>
7
#include <charconv>
8
#include <optional>
9
10
#include "glaze/core/seek.hpp"
11
#include "glaze/json/read.hpp"
12
#include "glaze/json/skip.hpp"
13
#include "glaze/util/parse.hpp"
14
#include "glaze/util/string_literal.hpp"
15
16
namespace glz
17
{
18
   namespace jmespath
19
   {
20
      enum struct tokenization_error {
21
         none, // No error
22
         unbalanced_brackets, // Mismatched '[' and ']'
23
         unbalanced_parentheses, // Mismatched '(' and ')'
24
         unclosed_string, // String literal not properly closed
25
         invalid_escape_sequence, // Invalid escape sequence in string
26
         unexpected_delimiter, // Unexpected character encountered (e.g., consecutive delimiters)
27
      };
28
29
      struct tokenization_result
30
      {
31
         std::string_view first;
32
         std::string_view second;
33
         tokenization_error error;
34
      };
35
36
      /**
37
       * @brief Trims leading whitespace characters from a string_view.
38
       *
39
       * @param s The input string_view to trim.
40
       * @return A string_view with leading whitespace removed.
41
       */
42
      inline constexpr std::string_view trim_left(std::string_view s)
43
0
      {
44
0
         size_t start = 0;
45
0
         while (start < s.size() && (s[start] == ' ' || s[start] == '\t' || s[start] == '\n' || s[start] == '\r')) {
46
0
            start++;
47
0
         }
48
0
         return s.substr(start);
49
0
      }
50
51
      /**
52
       * @brief Splits a JMESPath expression into the first token and the remaining path with error handling.
53
       *
54
       * @param s The JMESPath expression to tokenize.
55
       * @return tokenization_result containing:
56
       *         - first: The first token of the expression.
57
       *         - second: The remaining expression after the first token.
58
       *         - error: tokenization_error indicating if an error occurred.
59
       */
60
      inline constexpr tokenization_result tokenize_jmes_path(std::string_view s)
61
0
      {
62
0
         if (s.empty()) {
63
0
            return {"", "", tokenization_error::none};
64
0
         }
65
0
66
0
         size_t pos = 0;
67
0
         size_t len = s.size();
68
0
         int bracket_level = 0;
69
0
         int parenthesis_level = 0;
70
0
         bool in_string = false;
71
0
         char string_delim = '\0';
72
0
73
0
         while (pos < len) {
74
0
            char current = s[pos];
75
0
76
0
            if (in_string) {
77
0
               if (current == string_delim) {
78
0
                  // Check for escaped delimiter
79
0
                  size_t backslashes = 0;
80
0
                  size_t temp = pos;
81
0
                  while (temp > 0 && s[--temp] == '\\') {
82
0
                     backslashes++;
83
0
                  }
84
0
                  if (backslashes % 2 == 0) {
85
0
                     in_string = false;
86
0
                  }
87
0
               }
88
0
               else if (current == '\\') {
89
0
                  // Validate escape sequence
90
0
                  if (pos + 1 >= len) {
91
0
                     return {"", "", tokenization_error::invalid_escape_sequence};
92
0
                  }
93
0
                  char next_char = s[pos + 1];
94
0
                  // Simple validation: allow known escape characters
95
0
                  if (next_char != '"' && next_char != '\'' && next_char != '\\' && next_char != '/' &&
96
0
                      next_char != 'b' && next_char != 'f' && next_char != 'n' && next_char != 'r' &&
97
0
                      next_char != 't' && next_char != 'u') {
98
0
                     return {"", "", tokenization_error::invalid_escape_sequence};
99
0
                  }
100
0
               }
101
0
               pos++;
102
0
               continue;
103
0
            }
104
0
105
0
            switch (current) {
106
0
            case '"':
107
0
            case '\'':
108
0
               in_string = true;
109
0
               string_delim = current;
110
0
               pos++;
111
0
               break;
112
0
            case '[':
113
0
               bracket_level++;
114
0
               pos++;
115
0
               break;
116
0
            case ']':
117
0
               if (bracket_level > 0) {
118
0
                  bracket_level--;
119
0
               }
120
0
               else {
121
0
                  return {"", "", tokenization_error::unbalanced_brackets};
122
0
               }
123
0
               pos++;
124
0
               break;
125
0
            case '(':
126
0
               parenthesis_level++;
127
0
               pos++;
128
0
               break;
129
0
            case ')':
130
0
               if (parenthesis_level > 0) {
131
0
                  parenthesis_level--;
132
0
               }
133
0
               else {
134
0
                  return {"", "", tokenization_error::unbalanced_parentheses};
135
0
               }
136
0
               pos++;
137
0
               break;
138
0
            case '.':
139
0
            case '|':
140
0
               if (bracket_level == 0 && parenthesis_level == 0) {
141
0
                  // Split here
142
0
                  return {s.substr(0, pos), s.substr(pos, len - pos), tokenization_error::none};
143
0
               }
144
0
               pos++;
145
0
               break;
146
0
            default:
147
0
               pos++;
148
0
               break;
149
0
            }
150
0
         }
151
0
152
0
         if (in_string) {
153
0
            return {"", "", tokenization_error::unclosed_string};
154
0
         }
155
0
156
0
         if (bracket_level != 0) {
157
0
            return {"", "", tokenization_error::unbalanced_brackets};
158
0
         }
159
0
160
0
         if (parenthesis_level != 0) {
161
0
            return {"", "", tokenization_error::unbalanced_parentheses};
162
0
         }
163
0
164
0
         // If no delimiter found, return the whole string as first token
165
0
         return {s, "", tokenization_error::none};
166
0
      }
167
168
      inline constexpr tokenization_error finalize_tokens(std::vector<std::string_view>& tokens)
169
0
      {
170
0
         std::vector<std::string_view> final_tokens;
171
0
         final_tokens.reserve(tokens.size()); // at least
172
0
173
0
         for (auto token : tokens) {
174
0
            size_t start = 0;
175
0
            while (start < token.size()) {
176
0
               // Find the next '['
177
0
               auto open = token.find('[', start);
178
0
               if (open == std::string_view::npos) {
179
0
                  // No more bracketed segments
180
0
                  if (start < token.size()) {
181
0
                     // Add remaining part (if not empty)
182
0
                     final_tokens.push_back(token.substr(start));
183
0
                  }
184
0
                  break; // move to next token
185
0
               }
186
0
               else {
187
0
                  // If there's a key part before the bracket, add it
188
0
                  if (open > start) {
189
0
                     final_tokens.push_back(token.substr(start, open - start));
190
0
                  }
191
0
                  // Now find the closing bracket ']'
192
0
                  auto close = token.find(']', open + 1);
193
0
                  if (close == std::string_view::npos) {
194
0
                     // Mismatched bracket
195
0
                     return tokenization_error::unbalanced_brackets;
196
0
                  }
197
0
                  // Extract the bracketed token: e.g. [0]
198
0
                  final_tokens.push_back(token.substr(open, close - open + 1));
199
0
                  start = close + 1; // continue after the ']'
200
0
               }
201
0
            }
202
0
         }
203
0
204
0
         tokens = std::move(final_tokens);
205
0
         return tokenization_error::none;
206
0
      }
207
208
      /**
209
       * @brief Recursively tokenizes a full JMESPath expression into all its tokens with error handling.
210
       *
211
       * @param expression The complete JMESPath expression to tokenize.
212
       * @param tokens A vector to store all tokens in the order they appear.
213
       * @param error An output parameter to capture any tokenization errors.
214
       * @return true if tokenization succeeded without errors, false otherwise.
215
       */
216
      inline constexpr jmespath::tokenization_error tokenize_full_jmespath(std::string_view expression,
217
                                                                           std::vector<std::string_view>& tokens)
218
0
      {
219
0
         tokens.clear();
220
0
         auto remaining = expression;
221
0
222
0
         while (!remaining.empty()) {
223
0
            tokenization_result result = tokenize_jmes_path(remaining);
224
0
            if (result.error != tokenization_error::none) {
225
0
               return result.error;
226
0
            }
227
0
228
0
            if (result.first.empty()) {
229
0
               return tokenization_error::unexpected_delimiter;
230
0
            }
231
0
232
0
            tokens.emplace_back(result.first);
233
0
234
0
            if (!result.second.empty()) {
235
0
               char delimiter = result.second.front();
236
0
               if (delimiter == '.' || delimiter == '|') {
237
0
                  remaining = result.second.substr(1);
238
0
                  remaining = trim_left(remaining);
239
0
                  if (!remaining.empty() && (remaining.front() == '.' || remaining.front() == '|')) {
240
0
                     return tokenization_error::unexpected_delimiter;
241
0
                  }
242
0
               }
243
0
               else {
244
0
                  return tokenization_error::unexpected_delimiter;
245
0
               }
246
0
            }
247
0
            else {
248
0
               break;
249
0
            }
250
0
         }
251
0
252
0
         // New step: finalize the tokens by splitting multiple bracket accesses
253
0
         auto err = finalize_tokens(tokens);
254
0
         if (err != jmespath::tokenization_error::none) {
255
0
            return err;
256
0
         }
257
0
258
0
         return tokenization_error::none;
259
0
      }
260
261
      template <const std::string_view& expression>
262
      consteval auto tokenize_as_array()
263
      {
264
         constexpr auto N = [] {
265
            std::vector<std::string_view> tokens;
266
            auto err = tokenize_full_jmespath(expression, tokens);
267
            if (err != tokenization_error::none) {
268
               std::abort();
269
            }
270
            return tokens.size();
271
         }();
272
273
         std::vector<std::string_view> tokens;
274
         auto err = tokenize_full_jmespath(expression, tokens);
275
         if (err != tokenization_error::none) {
276
            std::abort();
277
         }
278
279
         std::array<std::string_view, N> arr{};
280
         for (std::size_t i = 0; i < N; ++i) {
281
            arr[i] = tokens[i];
282
         }
283
         return arr; // Vector destroyed here, leaving only the array.
284
      }
285
286
      struct ArrayParseResult
287
      {
288
         bool is_array_access = false; // True if "key[...]"
289
         bool error = false; // True if parsing encountered an error
290
         std::string_view key; // The part before the first '['
291
         std::optional<int32_t> start; // For a single index or slice start
292
         std::optional<int32_t> end; // For slice end
293
         std::optional<int32_t> step; // For slice step
294
         size_t colon_count = 0; // Number of ':' characters found inside the brackets
295
      };
296
297
      inline constexpr std::optional<int> parse_int(std::string_view s)
298
0
      {
299
0
         if (s.empty()) {
300
0
            return std::nullopt;
301
0
         }
302
0
         int value;
303
0
         auto result = detail::from_chars(s.data(), s.data() + s.size(), value);
304
0
         if (result.ec == std::errc()) {
305
0
            return value;
306
0
         }
307
0
         return std::nullopt;
308
0
      }
309
310
      // Parse a token that may have array indexing or slicing.
311
      inline constexpr ArrayParseResult parse_jmespath_token(std::string_view token)
312
0
      {
313
0
         ArrayParseResult result;
314
0
315
0
         // Find the first '['
316
0
         auto open_pos = token.find('[');
317
0
         if (open_pos == std::string_view::npos) {
318
0
            // No array access, just a key.
319
0
            result.key = token;
320
0
            return result;
321
0
         }
322
0
323
0
         auto close_pos = token.rfind(']');
324
0
         if (close_pos == std::string_view::npos || close_pos < open_pos) {
325
0
            // Mismatched brackets -> error
326
0
            result.key = token.substr(0, open_pos);
327
0
            result.is_array_access = true;
328
0
            result.error = true;
329
0
            return result;
330
0
         }
331
0
332
0
         result.is_array_access = true;
333
0
         result.key = token.substr(0, open_pos);
334
0
         auto inside = token.substr(open_pos + 1, close_pos - (open_pos + 1));
335
0
         if (inside.empty()) {
336
0
            // Empty inside "[]" is invalid
337
0
            result.error = true;
338
0
            return result;
339
0
         }
340
0
341
0
         // Count colons to determine if it's a slice
342
0
         size_t colon_count = 0;
343
0
         for (char c : inside) {
344
0
            if (c == ':') {
345
0
               colon_count++;
346
0
            }
347
0
         }
348
0
         result.colon_count = colon_count;
349
0
350
0
         // Helper lambda to parse slice parts
351
0
         auto parse_slice = [&](std::string_view inside) {
352
0
            std::string_view parts[3];
353
0
            {
354
0
               size_t start_idx = 0;
355
0
               int idx = 0;
356
0
               for (size_t i = 0; i <= inside.size(); ++i) {
357
0
                  if (i == inside.size() || inside[i] == ':') {
358
0
                     if (idx < 3) {
359
0
                        parts[idx] = inside.substr(start_idx, i - start_idx);
360
0
                        idx++;
361
0
                     }
362
0
                     start_idx = i + 1;
363
0
                  }
364
0
               }
365
0
            }
366
0
367
0
            // Parse start
368
0
            if (!parts[0].empty()) {
369
0
               auto val = parse_int(parts[0]);
370
0
               if (!val.has_value()) {
371
0
                  result.error = true;
372
0
               }
373
0
               else {
374
0
                  result.start = val;
375
0
               }
376
0
            }
377
0
378
0
            // Parse end
379
0
            if (!parts[1].empty()) {
380
0
               auto val = parse_int(parts[1]);
381
0
               if (!val.has_value()) {
382
0
                  result.error = true;
383
0
               }
384
0
               else {
385
0
                  result.end = val;
386
0
               }
387
0
            }
388
0
389
0
            // Parse step
390
0
            if (colon_count == 2 && !parts[2].empty()) {
391
0
               auto val = parse_int(parts[2]);
392
0
               if (!val.has_value()) {
393
0
                  result.error = true;
394
0
               }
395
0
               else {
396
0
                  result.step = val;
397
0
               }
398
0
            }
399
0
         };
400
0
401
0
         if (colon_count == 0) {
402
0
            // single index
403
0
            auto val = parse_int(inside);
404
0
            if (!val.has_value()) {
405
0
               result.error = true;
406
0
            }
407
0
            else {
408
0
               result.start = val;
409
0
            }
410
0
         }
411
0
         else if (colon_count == 1 || colon_count == 2) {
412
0
            // slice
413
0
            parse_slice(inside);
414
0
         }
415
0
         else {
416
0
            // More than 2 colons is invalid
417
0
            result.error = true;
418
0
         }
419
0
420
0
         return result;
421
0
      }
422
   }
423
424
   namespace detail
425
   {
426
      template <auto Opts = opts{}, class T>
427
         requires(Opts.format == JSON && not readable_array_t<T> &&
428
                  not(tuple_t<std::decay_t<T>> || is_std_tuple<std::decay_t<T>>))
429
      inline void handle_slice(const jmespath::ArrayParseResult&, T&&, context& ctx, auto&&, auto&&)
430
      {
431
         ctx.error = error_code::syntax_error;
432
      }
433
434
      template <auto Opts = opts{}, class T>
435
         requires(Opts.format == JSON && (tuple_t<std::decay_t<T>> || is_std_tuple<std::decay_t<T>>))
436
      inline void handle_slice(const jmespath::ArrayParseResult& decomposed_key, T&& value, context& ctx, auto&& it,
437
                               auto&& end)
438
      {
439
         if (skip_ws<Opts>(ctx, it, end)) {
440
            return;
441
         }
442
443
         // Determine slice parameters
444
         int32_t step_idx = decomposed_key.step.value_or(1);
445
         bool has_negative_index = (decomposed_key.start.value_or(0) < 0) || (decomposed_key.end.value_or(0) < 0);
446
447
         // Only support step == 1 and positive indices for now for tuples
448
         if (step_idx != 1 || has_negative_index) {
449
            ctx.error = error_code::syntax_error;
450
            return;
451
         }
452
453
         const int32_t start_idx = decomposed_key.start.value_or(0);
454
         const int32_t end_idx = decomposed_key.end.value_or((std::numeric_limits<int32_t>::max)());
455
456
         if (*it == ']') {
457
            ++it;
458
            return;
459
         }
460
461
         int32_t current_index = 0;
462
463
         // Iterate tuple elements
464
         using TupleType = std::decay_t<T>;
465
         constexpr size_t N = glz::tuple_size_v<TupleType>;
466
467
         for_each<N>([&]<size_t I>() {
468
            if (bool(ctx.error)) return;
469
470
            int32_t target_idx = start_idx + I;
471
472
            // If target is beyond the slice request
473
            if (target_idx >= end_idx) {
474
               return;
475
            }
476
477
            // Skip until target_idx
478
            while (current_index < target_idx) {
479
               if (*it == ']') {
480
                  // Array ended before we reached target
481
                  ctx.error = error_code::array_element_not_found;
482
                  return;
483
               }
484
               skip_value<JSON>::op<Opts>(ctx, it, end);
485
               if (bool(ctx.error)) return;
486
               if (*it == ',') {
487
                  ++it;
488
                  skip_ws<Opts>(ctx, it, end);
489
               }
490
               else if (*it == ']') {
491
                  return;
492
               }
493
               current_index++;
494
            }
495
496
            // Now current_index == target_idx
497
            if (*it == ']') {
498
               ctx.error = error_code::array_element_not_found;
499
               return;
500
            }
501
502
            if constexpr (is_std_tuple<TupleType>) {
503
               parse<Opts.format>::template op<Opts>(std::get<I>(value), ctx, it, end);
504
            }
505
            else {
506
               parse<Opts.format>::template op<Opts>(glz::get<I>(value), ctx, it, end);
507
            }
508
            if (bool(ctx.error)) return;
509
510
            if (*it == ',') {
511
               ++it;
512
               skip_ws<Opts>(ctx, it, end);
513
            }
514
            current_index++;
515
         });
516
517
         if (bool(ctx.error)) return;
518
519
         // Consume remainder of array
520
         while (*it != ']') {
521
            skip_value<JSON>::op<Opts>(ctx, it, end);
522
            if (bool(ctx.error)) return;
523
            if (*it == ',') {
524
               ++it;
525
               skip_ws<Opts>(ctx, it, end);
526
            }
527
         }
528
         ++it; // consume ']'
529
      }
530
531
      template <auto Opts = opts{}, class T>
532
         requires(Opts.format == JSON && readable_array_t<T>)
533
      inline void handle_slice(const jmespath::ArrayParseResult& decomposed_key, T&& value, context& ctx, auto&& it,
534
                               auto&& end)
535
      {
536
         if (skip_ws<Opts>(ctx, it, end)) {
537
            return;
538
         }
539
540
         // Determine slice parameters
541
         int32_t step_idx = decomposed_key.step.value_or(1);
542
         bool has_negative_index = (decomposed_key.start.value_or(0) < 0) || (decomposed_key.end.value_or(0) < 0);
543
544
         // If we have negative indices or step != 1, fall back to the original method (read all then slice)
545
         if (step_idx != 1 || has_negative_index) {
546
            // Original fallback behavior:
547
            // Read entire array into value first
548
            value.clear();
549
            if (*it == ']') {
550
               // empty array
551
               ++it; // consume ']'
552
            }
553
            else {
554
               while (true) {
555
                  parse<Opts.format>::template op<Opts>(value.emplace_back(), ctx, it, end);
556
                  if (bool(ctx.error)) [[unlikely]]
557
                     return;
558
559
                  if (skip_ws<Opts>(ctx, it, end)) {
560
                     return;
561
                  }
562
                  if (*it == ']') {
563
                     ++it;
564
                     break;
565
                  }
566
                  if (*it != ',') {
567
                     ctx.error = error_code::parse_error;
568
                     return;
569
                  }
570
                  ++it;
571
                  if (skip_ws<Opts>(ctx, it, end)) {
572
                     return;
573
                  }
574
               }
575
            }
576
577
            // Now do the slicing
578
            const int32_t size = static_cast<int32_t>(value.size());
579
            auto wrap_index = [&](int32_t idx) {
580
               if (idx < 0) idx += size;
581
               return std::clamp(idx, int32_t{0}, size);
582
            };
583
584
            const int32_t start_idx = wrap_index(decomposed_key.start.value_or(0));
585
            const int32_t end_idx = wrap_index(decomposed_key.end.value_or(size));
586
587
            if (step_idx == 1) {
588
               if (start_idx < end_idx) {
589
                  if (start_idx > 0) {
590
                     value.erase(value.begin(), value.begin() + start_idx);
591
                  }
592
                  if (static_cast<size_t>(end_idx - start_idx) < value.size()) {
593
                     value.erase(value.begin() + (end_idx - start_idx), value.end());
594
                  }
595
               }
596
               else {
597
                  value.clear();
598
               }
599
            }
600
            else {
601
               // For steps != 1 (or negative steps), the fallback path was already chosen.
602
               // Just apply the same logic as before.
603
               std::size_t dest = 0;
604
               if (step_idx > 0) {
605
                  for (int32_t i = start_idx; i < end_idx; i += step_idx) {
606
                     value[dest++] = std::move(value[i]);
607
                  }
608
               }
609
               else {
610
                  for (int32_t i = start_idx; i > end_idx; i += step_idx) {
611
                     value[dest++] = std::move(value[i]);
612
                  }
613
               }
614
               value.resize(dest);
615
            }
616
617
            return;
618
         }
619
620
         // If we reach here, step == 1 and no negative indices, so we can do partial reading.
621
         value.clear();
622
         const int32_t start_idx = decomposed_key.start.value_or(0);
623
         const int32_t end_idx = decomposed_key.end.value_or((std::numeric_limits<int32_t>::max)());
624
625
         // If empty array
626
         if (*it == ']') {
627
            ++it; // consume ']'
628
            return;
629
         }
630
631
         // We'll read elements and track their index
632
         int32_t current_index = 0;
633
         while (true) {
634
            if (skip_ws<Opts>(ctx, it, end)) {
635
               return;
636
            }
637
638
            // Decide whether we read or skip this element
639
            if (current_index < start_idx) {
640
               // Skip this element
641
               skip_value<JSON>::op<Opts>(ctx, it, end);
642
               if (bool(ctx.error)) [[unlikely]]
643
                  return;
644
            }
645
            else if (current_index >= start_idx && current_index < end_idx) {
646
               // Read this element into value
647
               parse<Opts.format>::template op<Opts>(value.emplace_back(), ctx, it, end);
648
               if (bool(ctx.error)) [[unlikely]]
649
                  return;
650
            }
651
            else {
652
               // current_index >= end_idx, we can skip reading into value
653
               skip_value<JSON>::op<Opts>(ctx, it, end);
654
               if (bool(ctx.error)) [[unlikely]]
655
                  return;
656
            }
657
658
            if (skip_ws<Opts>(ctx, it, end)) {
659
               return;
660
            }
661
            if (*it == ']') {
662
               ++it; // finished reading array
663
               break;
664
            }
665
            if (*it != ',') {
666
               ctx.error = error_code::parse_error;
667
               return;
668
            }
669
            ++it; // consume ','
670
            if (skip_ws<Opts>(ctx, it, end)) {
671
               return;
672
            }
673
674
            ++current_index;
675
         }
676
      }
677
   }
678
679
   // Read into a C++ type given a path denoted by a JMESPath query
680
   template <string_literal Path, auto Options = opts{}, class T, contiguous Buffer>
681
      requires(Options.format == JSON)
682
   [[nodiscard]] inline error_ctx read_jmespath(T&& value, Buffer&& buffer)
683
   {
684
      static constexpr auto S = chars<Path>;
685
      static constexpr auto tokens = jmespath::tokenize_as_array<S>();
686
      static constexpr auto N = tokens.size();
687
688
      constexpr bool use_padded = resizable<Buffer> && non_const_buffer<Buffer> && !check_disable_padding(Options);
689
690
      static constexpr auto Opts = use_padded ? is_padded_on<Options>() : is_padded_off<Options>();
691
692
      if constexpr (use_padded) {
693
         // Pad the buffer for SWAR
694
         buffer.resize(buffer.size() + padding_bytes);
695
      }
696
      auto p = read_iterators<Opts>(buffer);
697
      auto it = p.first;
698
      auto end = p.second;
699
      auto start = it;
700
701
      context ctx{};
702
703
      if constexpr (N == 0) {
704
         parse<Opts.format>::template op<Opts>(value, ctx, it, end);
705
      }
706
      else {
707
         using namespace glz::detail;
708
709
         skip_ws<Opts>(ctx, it, end);
710
711
         for_each<N>([&]<auto I>() {
712
            if (bool(ctx.error)) [[unlikely]] {
713
               return;
714
            }
715
716
            static constexpr auto decomposed_key = jmespath::parse_jmespath_token(tokens[I]);
717
            static constexpr auto key = decomposed_key.key;
718
719
            if constexpr (decomposed_key.is_array_access) {
720
               // If we have a key, that means we're looking into an object like: key[0:5]
721
               if constexpr (key.empty()) {
722
                  if (skip_ws<Opts>(ctx, it, end)) {
723
                     return;
724
                  }
725
                  // We expect the JSON at this level to be an array
726
                  if (match_invalid_end<'[', Opts>(ctx, it, end)) {
727
                     return;
728
                  }
729
730
                  // If this is a slice (colon_count > 0)
731
                  if constexpr (decomposed_key.colon_count > 0) {
732
                     detail::handle_slice<Opts>(decomposed_key, value, ctx, it, end);
733
                  }
734
                  else {
735
                     // SINGLE INDEX SCENARIO (no slice, just an index)
736
                     if constexpr (decomposed_key.start.has_value()) {
737
                        constexpr auto n = decomposed_key.start.value();
738
739
                        if constexpr (I == (N - 1)) {
740
                           // Skip until we reach the target element n
741
                           for (int32_t i = 0; i < n; ++i) {
742
                              skip_value<JSON>::op<Opts>(ctx, it, end);
743
                              if (bool(ctx.error)) [[unlikely]]
744
                                 return;
745
746
                              if (skip_ws<Opts>(ctx, it, end)) {
747
                                 return;
748
                              }
749
                              if (*it != ',') {
750
                                 ctx.error = error_code::array_element_not_found;
751
                                 return;
752
                              }
753
                              ++it;
754
                              if (skip_ws<Opts>(ctx, it, end)) {
755
                                 return;
756
                              }
757
                           }
758
759
                           // Now read the element at index n
760
                           parse<Opts.format>::template op<Opts>(value, ctx, it, end);
761
                        }
762
                        else {
763
                           // Not the last token. We must still parse the element at index n so the next indexing can
764
                           // proceed.
765
                           for (int32_t i = 0; i < n; ++i) {
766
                              skip_value<JSON>::op<Opts>(ctx, it, end);
767
                              if (bool(ctx.error)) [[unlikely]]
768
                                 return;
769
770
                              if (skip_ws<Opts>(ctx, it, end)) {
771
                                 return;
772
                              }
773
                              if (*it != ',') {
774
                                 ctx.error = error_code::array_element_not_found;
775
                                 return;
776
                              }
777
                              ++it;
778
                              if (skip_ws<Opts>(ctx, it, end)) {
779
                                 return;
780
                              }
781
                           }
782
                        }
783
                     }
784
                     else {
785
                        ctx.error = error_code::array_element_not_found;
786
                        return;
787
                     }
788
                  }
789
790
                  // After handling the array access, we're done for this token
791
                  return;
792
               }
793
               else {
794
                  // Object scenario with a key, like: key[0:5]
795
                  if (match_invalid_end<'{', Opts>(ctx, it, end)) {
796
                     return;
797
                  }
798
799
                  while (true) {
800
                     if (skip_ws<Opts>(ctx, it, end)) {
801
                        return;
802
                     }
803
                     if (match<'"'>(ctx, it)) {
804
                        return;
805
                     }
806
807
                     auto* start = it;
808
                     skip_string_view<Opts>(ctx, it, end);
809
                     if (bool(ctx.error)) [[unlikely]]
810
                        return;
811
                     const sv k = {start, size_t(it - start)};
812
                     ++it;
813
814
                     if (key.size() == k.size() && comparitor<key>(k.data())) {
815
                        if (skip_ws<Opts>(ctx, it, end)) {
816
                           return;
817
                        }
818
                        if (match_invalid_end<':', Opts>(ctx, it, end)) {
819
                           return;
820
                        }
821
                        if (skip_ws<Opts>(ctx, it, end)) {
822
                           return;
823
                        }
824
                        if (match_invalid_end<'[', Opts>(ctx, it, end)) {
825
                           return;
826
                        }
827
828
                        // Distinguish single index vs slice using colon_count
829
                        if constexpr (decomposed_key.colon_count > 0) {
830
                           detail::handle_slice<Opts, decomposed_key>(value, ctx, it, end);
831
                        }
832
                        else {
833
                           // SINGLE INDEX SCENARIO (colon_count == 0)
834
                           if constexpr (decomposed_key.start.has_value()) {
835
                              // Skip until we reach the target element
836
                              constexpr auto n = decomposed_key.start.value();
837
                              for (int32_t i = 0; i < n; ++i) {
838
                                 skip_value<JSON>::op<Opts>(ctx, it, end);
839
                                 if (bool(ctx.error)) [[unlikely]]
840
                                    return;
841
842
                                 if (skip_ws<Opts>(ctx, it, end)) {
843
                                    return;
844
                                 }
845
                                 if (*it != ',') {
846
                                    ctx.error = error_code::array_element_not_found;
847
                                    return;
848
                                 }
849
                                 ++it;
850
                                 if (skip_ws<Opts>(ctx, it, end)) {
851
                                    return;
852
                                 }
853
                              }
854
855
                              if (skip_ws<Opts>(ctx, it, end)) {
856
                                 return;
857
                              }
858
859
                              if constexpr (I == (N - 1)) {
860
                                 parse<Opts.format>::template op<Opts>(value, ctx, it, end);
861
                              }
862
                              return;
863
                           }
864
                           else {
865
                              ctx.error = error_code::array_element_not_found;
866
                              return;
867
                           }
868
                        }
869
                     }
870
                     else {
871
                        skip_value<JSON>::op<Opts>(ctx, it, end);
872
                        if (bool(ctx.error)) [[unlikely]] {
873
                           return;
874
                        }
875
                        if (skip_ws<Opts>(ctx, it, end)) {
876
                           return;
877
                        }
878
                        if (*it != ',') {
879
                           ctx.error = error_code::key_not_found;
880
                           return;
881
                        }
882
                        ++it;
883
                     }
884
                  }
885
               }
886
            }
887
            else {
888
               // If it's not array access, we are dealing with an object key
889
               if (match_invalid_end<'{', Opts>(ctx, it, end)) {
890
                  return;
891
               }
892
893
               while (it < end) {
894
                  if (skip_ws<Opts>(ctx, it, end)) {
895
                     return;
896
                  }
897
                  if (match<'"'>(ctx, it)) {
898
                     return;
899
                  }
900
901
                  auto* start = it;
902
                  skip_string_view<Opts>(ctx, it, end);
903
                  if (bool(ctx.error)) [[unlikely]]
904
                     return;
905
                  const sv k = {start, size_t(it - start)};
906
                  ++it;
907
908
                  if (key.size() == k.size() && comparitor<key>(k.data())) {
909
                     if (skip_ws<Opts>(ctx, it, end)) {
910
                        return;
911
                     }
912
                     if (match_invalid_end<':', Opts>(ctx, it, end)) {
913
                        return;
914
                     }
915
                     if (skip_ws<Opts>(ctx, it, end)) {
916
                        return;
917
                     }
918
919
                     if constexpr (I == (N - 1)) {
920
                        parse<Opts.format>::template op<Opts>(value, ctx, it, end);
921
                     }
922
                     return;
923
                  }
924
                  else {
925
                     skip_value<JSON>::op<Opts>(ctx, it, end);
926
                     if (bool(ctx.error)) [[unlikely]] {
927
                        return;
928
                     }
929
                     if (skip_ws<Opts>(ctx, it, end)) {
930
                        return;
931
                     }
932
                     if (*it != ',') {
933
                        ctx.error = error_code::key_not_found;
934
                        return;
935
                     }
936
                     ++it;
937
                  }
938
               }
939
            }
940
         });
941
      }
942
943
      if constexpr (use_padded) {
944
         // Restore the original buffer state
945
         buffer.resize(buffer.size() - padding_bytes);
946
      }
947
948
      return {ctx.error, ctx.custom_error_message, size_t(it - start), ctx.includer_error};
949
   }
950
951
   // A "compiled" jmespath expression, which can be pre-computed for efficient traversal
952
   struct jmespath_expression
953
   {
954
      std::string_view path{};
955
      jmespath::tokenization_error error{};
956
      std::vector<std::string_view> tokens{}; // evaluated tokens
957
958
      jmespath_expression(const std::string_view input_path) noexcept : path(input_path)
959
0
      {
960
0
         error = jmespath::tokenize_full_jmespath(path, tokens);
961
0
      }
962
963
      template <size_t N>
964
      jmespath_expression(const char (&input_path)[N]) noexcept : path(input_path)
965
      {
966
         error = jmespath::tokenize_full_jmespath(path, tokens);
967
      }
968
      jmespath_expression(const jmespath_expression&) noexcept = default;
969
      jmespath_expression(jmespath_expression&&) noexcept = default;
970
      jmespath_expression& operator=(const jmespath_expression&) noexcept = default;
971
      jmespath_expression& operator=(jmespath_expression&&) noexcept = default;
972
   };
973
974
   // Read into a C++ type given a path denoted by a JMESPath query
975
   // This version supports a runtime path
976
   template <auto Options = opts{}, class T, contiguous Buffer>
977
      requires(Options.format == JSON)
978
   [[nodiscard]] inline error_ctx read_jmespath(const jmespath_expression& expression, T&& value, Buffer&& buffer)
979
   {
980
      if (bool(expression.error)) {
981
         return {error_code::syntax_error, "JMESPath invalid expression"};
982
      }
983
984
      const auto& tokens = expression.tokens;
985
      const auto N = tokens.size();
986
987
      constexpr bool use_padded = resizable<Buffer> && non_const_buffer<Buffer> && !check_disable_padding(Options);
988
      static constexpr auto Opts = use_padded ? is_padded_on<Options>() : is_padded_off<Options>();
989
990
      if constexpr (use_padded) {
991
         // Pad the buffer for SWAR
992
         buffer.resize(buffer.size() + padding_bytes);
993
      }
994
      auto p = read_iterators<Opts>(buffer);
995
      auto it = p.first;
996
      auto end = p.second;
997
      auto start = it;
998
999
      context ctx{};
1000
1001
      if (N == 0) {
1002
         parse<Opts.format>::template op<Opts>(value, ctx, it, end);
1003
      }
1004
      else {
1005
         using namespace glz::detail;
1006
1007
         skip_ws<Opts>(ctx, it, end);
1008
1009
         for (size_t I = 0; I < N; ++I) {
1010
            if (bool(ctx.error)) [[unlikely]] {
1011
               break;
1012
            }
1013
1014
            [&] {
1015
               const auto decomposed_key = jmespath::parse_jmespath_token(tokens[I]);
1016
               const auto& key = decomposed_key.key;
1017
1018
               if (decomposed_key.is_array_access) {
1019
                  if (key.empty()) {
1020
                     // Top-level array scenario
1021
                     if (skip_ws<Opts>(ctx, it, end)) {
1022
                        return;
1023
                     }
1024
                     if (match_invalid_end<'[', Opts>(ctx, it, end)) {
1025
                        return;
1026
                     }
1027
1028
                     if (decomposed_key.colon_count > 0) {
1029
                        // Slice scenario
1030
                        detail::handle_slice(decomposed_key, value, ctx, it, end);
1031
                        return;
1032
                     }
1033
                     else {
1034
                        // Single index scenario
1035
                        if (decomposed_key.start.has_value()) {
1036
                           const int32_t n = decomposed_key.start.value();
1037
1038
                           if (I == (N - 1)) {
1039
                              // Skip until we reach the target element n
1040
                              for (int32_t i = 0; i < n; ++i) {
1041
                                 skip_value<JSON>::op<Opts>(ctx, it, end);
1042
                                 if (bool(ctx.error)) [[unlikely]]
1043
                                    return;
1044
1045
                                 if (skip_ws<Opts>(ctx, it, end)) {
1046
                                    return;
1047
                                 }
1048
                                 if (*it != ',') {
1049
                                    ctx.error = error_code::array_element_not_found;
1050
                                    return;
1051
                                 }
1052
                                 ++it;
1053
                                 if (skip_ws<Opts>(ctx, it, end)) {
1054
                                    return;
1055
                                 }
1056
                              }
1057
1058
                              // Now read the element at index n
1059
                              parse<Opts.format>::template op<Opts>(value, ctx, it, end);
1060
                           }
1061
                           else {
1062
                              // Not the last token. We must still parse the element at index n so the next indexing can
1063
                              // proceed.
1064
                              for (int32_t i = 0; i < n; ++i) {
1065
                                 skip_value<JSON>::op<Opts>(ctx, it, end);
1066
                                 if (bool(ctx.error)) [[unlikely]]
1067
                                    return;
1068
1069
                                 if (skip_ws<Opts>(ctx, it, end)) {
1070
                                    return;
1071
                                 }
1072
                                 if (*it != ',') {
1073
                                    ctx.error = error_code::array_element_not_found;
1074
                                    return;
1075
                                 }
1076
                                 ++it;
1077
                                 if (skip_ws<Opts>(ctx, it, end)) {
1078
                                    return;
1079
                                 }
1080
                              }
1081
                           }
1082
                        }
1083
                        else {
1084
                           ctx.error = error_code::array_element_not_found;
1085
                           return;
1086
                        }
1087
                        return;
1088
                     }
1089
                  }
1090
                  else {
1091
                     // Object scenario: key[...]
1092
                     if (match_invalid_end<'{', Opts>(ctx, it, end)) {
1093
                        return;
1094
                     }
1095
1096
                     while (true) {
1097
                        if (skip_ws<Opts>(ctx, it, end)) {
1098
                           return;
1099
                        }
1100
                        if (match<'"'>(ctx, it)) {
1101
                           return;
1102
                        }
1103
1104
                        auto* start_pos = it;
1105
                        skip_string_view<Opts>(ctx, it, end);
1106
                        if (bool(ctx.error)) [[unlikely]]
1107
                           return;
1108
                        const sv k = {start_pos, size_t(it - start_pos)};
1109
                        ++it;
1110
1111
                        if (key.size() == k.size() && memcmp(key.data(), k.data(), key.size()) == 0) {
1112
                           if (skip_ws<Opts>(ctx, it, end)) {
1113
                              return;
1114
                           }
1115
                           if (match_invalid_end<':', Opts>(ctx, it, end)) {
1116
                              return;
1117
                           }
1118
                           if (skip_ws<Opts>(ctx, it, end)) {
1119
                              return;
1120
                           }
1121
                           if (match_invalid_end<'[', Opts>(ctx, it, end)) {
1122
                              return;
1123
                           }
1124
1125
                           if (decomposed_key.colon_count > 0) {
1126
                              // Slice scenario
1127
                              detail::handle_slice(decomposed_key, value, ctx, it, end);
1128
                              return;
1129
                           }
1130
                           else {
1131
                              // Single index scenario
1132
                              if (decomposed_key.start.has_value()) {
1133
                                 int32_t n = decomposed_key.start.value();
1134
                                 for (int32_t i = 0; i < n; ++i) {
1135
                                    skip_value<JSON>::op<Opts>(ctx, it, end);
1136
                                    if (bool(ctx.error)) [[unlikely]]
1137
                                       return;
1138
1139
                                    if (skip_ws<Opts>(ctx, it, end)) {
1140
                                       return;
1141
                                    }
1142
                                    if (*it != ',') {
1143
                                       ctx.error = error_code::array_element_not_found;
1144
                                       return;
1145
                                    }
1146
                                    ++it;
1147
                                    if (skip_ws<Opts>(ctx, it, end)) {
1148
                                       return;
1149
                                    }
1150
                                 }
1151
1152
                                 if (skip_ws<Opts>(ctx, it, end)) {
1153
                                    return;
1154
                                 }
1155
1156
                                 if (I == (N - 1)) {
1157
                                    parse<Opts.format>::template op<Opts>(value, ctx, it, end);
1158
                                 }
1159
                                 return;
1160
                              }
1161
                              else {
1162
                                 ctx.error = error_code::array_element_not_found;
1163
                                 return;
1164
                              }
1165
                           }
1166
                        }
1167
                        else {
1168
                           skip_value<JSON>::op<Opts>(ctx, it, end);
1169
                           if (bool(ctx.error)) [[unlikely]] {
1170
                              return;
1171
                           }
1172
                           if (skip_ws<Opts>(ctx, it, end)) {
1173
                              return;
1174
                           }
1175
                           if (*it != ',') {
1176
                              ctx.error = error_code::key_not_found;
1177
                              return;
1178
                           }
1179
                           ++it;
1180
                        }
1181
                     }
1182
                  }
1183
               }
1184
               else {
1185
                  // Non-array access: key-only navigation
1186
                  if (match_invalid_end<'{', Opts>(ctx, it, end)) {
1187
                     return;
1188
                  }
1189
1190
                  while (it < end) {
1191
                     if (skip_ws<Opts>(ctx, it, end)) {
1192
                        return;
1193
                     }
1194
                     if (match<'"'>(ctx, it)) {
1195
                        return;
1196
                     }
1197
1198
                     auto* start_pos = it;
1199
                     skip_string_view<Opts>(ctx, it, end);
1200
                     if (bool(ctx.error)) [[unlikely]]
1201
                        return;
1202
                     const sv k = {start_pos, size_t(it - start_pos)};
1203
                     ++it;
1204
1205
                     if (key.size() == k.size() && memcmp(key.data(), k.data(), key.size()) == 0) {
1206
                        if (skip_ws<Opts>(ctx, it, end)) {
1207
                           return;
1208
                        }
1209
                        if (match_invalid_end<':', Opts>(ctx, it, end)) {
1210
                           return;
1211
                        }
1212
                        if (skip_ws<Opts>(ctx, it, end)) {
1213
                           return;
1214
                        }
1215
1216
                        if (I == (N - 1)) {
1217
                           parse<Opts.format>::template op<Opts>(value, ctx, it, end);
1218
                        }
1219
                        return;
1220
                     }
1221
                     else {
1222
                        skip_value<JSON>::op<Opts>(ctx, it, end);
1223
                        if (bool(ctx.error)) [[unlikely]] {
1224
                           return;
1225
                        }
1226
                        if (skip_ws<Opts>(ctx, it, end)) {
1227
                           return;
1228
                        }
1229
                        if (*it != ',') {
1230
                           ctx.error = error_code::key_not_found;
1231
                           return;
1232
                        }
1233
                        ++it;
1234
                     }
1235
                  }
1236
1237
                  if (!bool(ctx.error)) {
1238
                     if (it == end) {
1239
                        ctx.error = error_code::unexpected_end;
1240
                     }
1241
                     else {
1242
                        ctx.error = error_code::key_not_found;
1243
                     }
1244
                  }
1245
               }
1246
            }();
1247
         }
1248
      }
1249
1250
      if constexpr (use_padded) {
1251
         // Restore the original buffer state
1252
         buffer.resize(buffer.size() - padding_bytes);
1253
      }
1254
1255
      return {ctx.error, ctx.custom_error_message, size_t(it - start), ctx.includer_error};
1256
   }
1257
1258
}