1 | #pragma once |
2 | |
3 | #include <algorithm> // all_of |
4 | #include <cassert> // assert |
5 | #include <cctype> // isdigit |
6 | #include <numeric> // accumulate |
7 | #include <string> // string |
8 | #include <utility> // move |
9 | #include <vector> // vector |
10 | |
11 | #include <nlohmann/detail/exceptions.hpp> |
12 | #include <nlohmann/detail/macro_scope.hpp> |
13 | #include <nlohmann/detail/value_t.hpp> |
14 | |
15 | namespace nlohmann |
16 | { |
17 | template<typename BasicJsonType> |
18 | class json_pointer |
19 | { |
20 | // allow basic_json to access private members |
21 | NLOHMANN_BASIC_JSON_TPL_DECLARATION |
22 | friend class basic_json; |
23 | |
24 | public: |
25 | /*! |
26 | @brief create JSON pointer |
27 | |
28 | Create a JSON pointer according to the syntax described in |
29 | [Section 3 of RFC6901](https://tools.ietf.org/html/rfc6901#section-3). |
30 | |
31 | @param[in] s string representing the JSON pointer; if omitted, the empty |
32 | string is assumed which references the whole JSON value |
33 | |
34 | @throw parse_error.107 if the given JSON pointer @a s is nonempty and does |
35 | not begin with a slash (`/`); see example below |
36 | |
37 | @throw parse_error.108 if a tilde (`~`) in the given JSON pointer @a s is |
38 | not followed by `0` (representing `~`) or `1` (representing `/`); see |
39 | example below |
40 | |
41 | @liveexample{The example shows the construction several valid JSON pointers |
42 | as well as the exceptional behavior.,json_pointer} |
43 | |
44 | @since version 2.0.0 |
45 | */ |
46 | explicit json_pointer(const std::string& s = "" ) |
47 | : reference_tokens(split(s)) |
48 | {} |
49 | |
50 | /*! |
51 | @brief return a string representation of the JSON pointer |
52 | |
53 | @invariant For each JSON pointer `ptr`, it holds: |
54 | @code {.cpp} |
55 | ptr == json_pointer(ptr.to_string()); |
56 | @endcode |
57 | |
58 | @return a string representation of the JSON pointer |
59 | |
60 | @liveexample{The example shows the result of `to_string`.,json_pointer__to_string} |
61 | |
62 | @since version 2.0.0 |
63 | */ |
64 | std::string to_string() const |
65 | { |
66 | return std::accumulate(reference_tokens.begin(), reference_tokens.end(), |
67 | std::string{}, |
68 | [](const std::string & a, const std::string & b) |
69 | { |
70 | return a + "/" + escape(b); |
71 | }); |
72 | } |
73 | |
74 | /// @copydoc to_string() |
75 | operator std::string() const |
76 | { |
77 | return to_string(); |
78 | } |
79 | |
80 | /*! |
81 | @brief append another JSON pointer at the end of this JSON pointer |
82 | |
83 | @param[in] ptr JSON pointer to append |
84 | @return JSON pointer with @a ptr appended |
85 | |
86 | @liveexample{The example shows the usage of `operator/=`.,json_pointer__operator_add} |
87 | |
88 | @complexity Linear in the length of @a ptr. |
89 | |
90 | @sa @ref operator/=(std::string) to append a reference token |
91 | @sa @ref operator/=(std::size_t) to append an array index |
92 | @sa @ref operator/(const json_pointer&, const json_pointer&) for a binary operator |
93 | |
94 | @since version 3.6.0 |
95 | */ |
96 | json_pointer& operator/=(const json_pointer& ptr) |
97 | { |
98 | reference_tokens.insert(reference_tokens.end(), |
99 | ptr.reference_tokens.begin(), |
100 | ptr.reference_tokens.end()); |
101 | return *this; |
102 | } |
103 | |
104 | /*! |
105 | @brief append an unescaped reference token at the end of this JSON pointer |
106 | |
107 | @param[in] token reference token to append |
108 | @return JSON pointer with @a token appended without escaping @a token |
109 | |
110 | @liveexample{The example shows the usage of `operator/=`.,json_pointer__operator_add} |
111 | |
112 | @complexity Amortized constant. |
113 | |
114 | @sa @ref operator/=(const json_pointer&) to append a JSON pointer |
115 | @sa @ref operator/=(std::size_t) to append an array index |
116 | @sa @ref operator/(const json_pointer&, std::size_t) for a binary operator |
117 | |
118 | @since version 3.6.0 |
119 | */ |
120 | json_pointer& operator/=(std::string token) |
121 | { |
122 | push_back(std::move(token)); |
123 | return *this; |
124 | } |
125 | |
126 | /*! |
127 | @brief append an array index at the end of this JSON pointer |
128 | |
129 | @param[in] array_index array index to append |
130 | @return JSON pointer with @a array_index appended |
131 | |
132 | @liveexample{The example shows the usage of `operator/=`.,json_pointer__operator_add} |
133 | |
134 | @complexity Amortized constant. |
135 | |
136 | @sa @ref operator/=(const json_pointer&) to append a JSON pointer |
137 | @sa @ref operator/=(std::string) to append a reference token |
138 | @sa @ref operator/(const json_pointer&, std::string) for a binary operator |
139 | |
140 | @since version 3.6.0 |
141 | */ |
142 | json_pointer& operator/=(std::size_t array_index) |
143 | { |
144 | return *this /= std::to_string(array_index); |
145 | } |
146 | |
147 | /*! |
148 | @brief create a new JSON pointer by appending the right JSON pointer at the end of the left JSON pointer |
149 | |
150 | @param[in] lhs JSON pointer |
151 | @param[in] rhs JSON pointer |
152 | @return a new JSON pointer with @a rhs appended to @a lhs |
153 | |
154 | @liveexample{The example shows the usage of `operator/`.,json_pointer__operator_add_binary} |
155 | |
156 | @complexity Linear in the length of @a lhs and @a rhs. |
157 | |
158 | @sa @ref operator/=(const json_pointer&) to append a JSON pointer |
159 | |
160 | @since version 3.6.0 |
161 | */ |
162 | friend json_pointer operator/(const json_pointer& lhs, |
163 | const json_pointer& rhs) |
164 | { |
165 | return json_pointer(lhs) /= rhs; |
166 | } |
167 | |
168 | /*! |
169 | @brief create a new JSON pointer by appending the unescaped token at the end of the JSON pointer |
170 | |
171 | @param[in] ptr JSON pointer |
172 | @param[in] token reference token |
173 | @return a new JSON pointer with unescaped @a token appended to @a ptr |
174 | |
175 | @liveexample{The example shows the usage of `operator/`.,json_pointer__operator_add_binary} |
176 | |
177 | @complexity Linear in the length of @a ptr. |
178 | |
179 | @sa @ref operator/=(std::string) to append a reference token |
180 | |
181 | @since version 3.6.0 |
182 | */ |
183 | friend json_pointer operator/(const json_pointer& ptr, std::string token) |
184 | { |
185 | return json_pointer(ptr) /= std::move(token); |
186 | } |
187 | |
188 | /*! |
189 | @brief create a new JSON pointer by appending the array-index-token at the end of the JSON pointer |
190 | |
191 | @param[in] ptr JSON pointer |
192 | @param[in] array_index array index |
193 | @return a new JSON pointer with @a array_index appended to @a ptr |
194 | |
195 | @liveexample{The example shows the usage of `operator/`.,json_pointer__operator_add_binary} |
196 | |
197 | @complexity Linear in the length of @a ptr. |
198 | |
199 | @sa @ref operator/=(std::size_t) to append an array index |
200 | |
201 | @since version 3.6.0 |
202 | */ |
203 | friend json_pointer operator/(const json_pointer& ptr, std::size_t array_index) |
204 | { |
205 | return json_pointer(ptr) /= array_index; |
206 | } |
207 | |
208 | /*! |
209 | @brief returns the parent of this JSON pointer |
210 | |
211 | @return parent of this JSON pointer; in case this JSON pointer is the root, |
212 | the root itself is returned |
213 | |
214 | @complexity Linear in the length of the JSON pointer. |
215 | |
216 | @liveexample{The example shows the result of `parent_pointer` for different |
217 | JSON Pointers.,json_pointer__parent_pointer} |
218 | |
219 | @since version 3.6.0 |
220 | */ |
221 | json_pointer parent_pointer() const |
222 | { |
223 | if (empty()) |
224 | { |
225 | return *this; |
226 | } |
227 | |
228 | json_pointer res = *this; |
229 | res.pop_back(); |
230 | return res; |
231 | } |
232 | |
233 | /*! |
234 | @brief remove last reference token |
235 | |
236 | @pre not `empty()` |
237 | |
238 | @liveexample{The example shows the usage of `pop_back`.,json_pointer__pop_back} |
239 | |
240 | @complexity Constant. |
241 | |
242 | @throw out_of_range.405 if JSON pointer has no parent |
243 | |
244 | @since version 3.6.0 |
245 | */ |
246 | void pop_back() |
247 | { |
248 | if (JSON_HEDLEY_UNLIKELY(empty())) |
249 | { |
250 | JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent" )); |
251 | } |
252 | |
253 | reference_tokens.pop_back(); |
254 | } |
255 | |
256 | /*! |
257 | @brief return last reference token |
258 | |
259 | @pre not `empty()` |
260 | @return last reference token |
261 | |
262 | @liveexample{The example shows the usage of `back`.,json_pointer__back} |
263 | |
264 | @complexity Constant. |
265 | |
266 | @throw out_of_range.405 if JSON pointer has no parent |
267 | |
268 | @since version 3.6.0 |
269 | */ |
270 | const std::string& back() const |
271 | { |
272 | if (JSON_HEDLEY_UNLIKELY(empty())) |
273 | { |
274 | JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent" )); |
275 | } |
276 | |
277 | return reference_tokens.back(); |
278 | } |
279 | |
280 | /*! |
281 | @brief append an unescaped token at the end of the reference pointer |
282 | |
283 | @param[in] token token to add |
284 | |
285 | @complexity Amortized constant. |
286 | |
287 | @liveexample{The example shows the result of `push_back` for different |
288 | JSON Pointers.,json_pointer__push_back} |
289 | |
290 | @since version 3.6.0 |
291 | */ |
292 | void push_back(const std::string& token) |
293 | { |
294 | reference_tokens.push_back(token); |
295 | } |
296 | |
297 | /// @copydoc push_back(const std::string&) |
298 | void push_back(std::string&& token) |
299 | { |
300 | reference_tokens.push_back(std::move(token)); |
301 | } |
302 | |
303 | /*! |
304 | @brief return whether pointer points to the root document |
305 | |
306 | @return true iff the JSON pointer points to the root document |
307 | |
308 | @complexity Constant. |
309 | |
310 | @exceptionsafety No-throw guarantee: this function never throws exceptions. |
311 | |
312 | @liveexample{The example shows the result of `empty` for different JSON |
313 | Pointers.,json_pointer__empty} |
314 | |
315 | @since version 3.6.0 |
316 | */ |
317 | bool empty() const noexcept |
318 | { |
319 | return reference_tokens.empty(); |
320 | } |
321 | |
322 | private: |
323 | /*! |
324 | @param[in] s reference token to be converted into an array index |
325 | |
326 | @return integer representation of @a s |
327 | |
328 | @throw out_of_range.404 if string @a s could not be converted to an integer |
329 | */ |
330 | static int array_index(const std::string& s) |
331 | { |
332 | std::size_t processed_chars = 0; |
333 | const int res = std::stoi(s, &processed_chars); |
334 | |
335 | // check if the string was completely read |
336 | if (JSON_HEDLEY_UNLIKELY(processed_chars != s.size())) |
337 | { |
338 | JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + s + "'" )); |
339 | } |
340 | |
341 | return res; |
342 | } |
343 | |
344 | json_pointer top() const |
345 | { |
346 | if (JSON_HEDLEY_UNLIKELY(empty())) |
347 | { |
348 | JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent" )); |
349 | } |
350 | |
351 | json_pointer result = *this; |
352 | result.reference_tokens = {reference_tokens[0]}; |
353 | return result; |
354 | } |
355 | |
356 | /*! |
357 | @brief create and return a reference to the pointed to value |
358 | |
359 | @complexity Linear in the number of reference tokens. |
360 | |
361 | @throw parse_error.109 if array index is not a number |
362 | @throw type_error.313 if value cannot be unflattened |
363 | */ |
364 | BasicJsonType& get_and_create(BasicJsonType& j) const |
365 | { |
366 | using size_type = typename BasicJsonType::size_type; |
367 | auto result = &j; |
368 | |
369 | // in case no reference tokens exist, return a reference to the JSON value |
370 | // j which will be overwritten by a primitive value |
371 | for (const auto& reference_token : reference_tokens) |
372 | { |
373 | switch (result->type()) |
374 | { |
375 | case detail::value_t::null: |
376 | { |
377 | if (reference_token == "0" ) |
378 | { |
379 | // start a new array if reference token is 0 |
380 | result = &result->operator[](0); |
381 | } |
382 | else |
383 | { |
384 | // start a new object otherwise |
385 | result = &result->operator[](reference_token); |
386 | } |
387 | break; |
388 | } |
389 | |
390 | case detail::value_t::object: |
391 | { |
392 | // create an entry in the object |
393 | result = &result->operator[](reference_token); |
394 | break; |
395 | } |
396 | |
397 | case detail::value_t::array: |
398 | { |
399 | // create an entry in the array |
400 | JSON_TRY |
401 | { |
402 | result = &result->operator[](static_cast<size_type>(array_index(reference_token))); |
403 | } |
404 | JSON_CATCH(std::invalid_argument&) |
405 | { |
406 | JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number" )); |
407 | } |
408 | break; |
409 | } |
410 | |
411 | /* |
412 | The following code is only reached if there exists a reference |
413 | token _and_ the current value is primitive. In this case, we have |
414 | an error situation, because primitive values may only occur as |
415 | single value; that is, with an empty list of reference tokens. |
416 | */ |
417 | default: |
418 | JSON_THROW(detail::type_error::create(313, "invalid value to unflatten" )); |
419 | } |
420 | } |
421 | |
422 | return *result; |
423 | } |
424 | |
425 | /*! |
426 | @brief return a reference to the pointed to value |
427 | |
428 | @note This version does not throw if a value is not present, but tries to |
429 | create nested values instead. For instance, calling this function |
430 | with pointer `"/this/that"` on a null value is equivalent to calling |
431 | `operator[]("this").operator[]("that")` on that value, effectively |
432 | changing the null value to an object. |
433 | |
434 | @param[in] ptr a JSON value |
435 | |
436 | @return reference to the JSON value pointed to by the JSON pointer |
437 | |
438 | @complexity Linear in the length of the JSON pointer. |
439 | |
440 | @throw parse_error.106 if an array index begins with '0' |
441 | @throw parse_error.109 if an array index was not a number |
442 | @throw out_of_range.404 if the JSON pointer can not be resolved |
443 | */ |
444 | BasicJsonType& get_unchecked(BasicJsonType* ptr) const |
445 | { |
446 | using size_type = typename BasicJsonType::size_type; |
447 | for (const auto& reference_token : reference_tokens) |
448 | { |
449 | // convert null values to arrays or objects before continuing |
450 | if (ptr->is_null()) |
451 | { |
452 | // check if reference token is a number |
453 | const bool nums = |
454 | std::all_of(reference_token.begin(), reference_token.end(), |
455 | [](const unsigned char x) |
456 | { |
457 | return std::isdigit(x); |
458 | }); |
459 | |
460 | // change value to array for numbers or "-" or to object otherwise |
461 | *ptr = (nums or reference_token == "-" ) |
462 | ? detail::value_t::array |
463 | : detail::value_t::object; |
464 | } |
465 | |
466 | switch (ptr->type()) |
467 | { |
468 | case detail::value_t::object: |
469 | { |
470 | // use unchecked object access |
471 | ptr = &ptr->operator[](reference_token); |
472 | break; |
473 | } |
474 | |
475 | case detail::value_t::array: |
476 | { |
477 | // error condition (cf. RFC 6901, Sect. 4) |
478 | if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) |
479 | { |
480 | JSON_THROW(detail::parse_error::create(106, 0, |
481 | "array index '" + reference_token + |
482 | "' must not begin with '0'" )); |
483 | } |
484 | |
485 | if (reference_token == "-" ) |
486 | { |
487 | // explicitly treat "-" as index beyond the end |
488 | ptr = &ptr->operator[](ptr->m_value.array->size()); |
489 | } |
490 | else |
491 | { |
492 | // convert array index to number; unchecked access |
493 | JSON_TRY |
494 | { |
495 | ptr = &ptr->operator[]( |
496 | static_cast<size_type>(array_index(reference_token))); |
497 | } |
498 | JSON_CATCH(std::invalid_argument&) |
499 | { |
500 | JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number" )); |
501 | } |
502 | } |
503 | break; |
504 | } |
505 | |
506 | default: |
507 | JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'" )); |
508 | } |
509 | } |
510 | |
511 | return *ptr; |
512 | } |
513 | |
514 | /*! |
515 | @throw parse_error.106 if an array index begins with '0' |
516 | @throw parse_error.109 if an array index was not a number |
517 | @throw out_of_range.402 if the array index '-' is used |
518 | @throw out_of_range.404 if the JSON pointer can not be resolved |
519 | */ |
520 | BasicJsonType& get_checked(BasicJsonType* ptr) const |
521 | { |
522 | using size_type = typename BasicJsonType::size_type; |
523 | for (const auto& reference_token : reference_tokens) |
524 | { |
525 | switch (ptr->type()) |
526 | { |
527 | case detail::value_t::object: |
528 | { |
529 | // note: at performs range check |
530 | ptr = &ptr->at(reference_token); |
531 | break; |
532 | } |
533 | |
534 | case detail::value_t::array: |
535 | { |
536 | if (JSON_HEDLEY_UNLIKELY(reference_token == "-" )) |
537 | { |
538 | // "-" always fails the range check |
539 | JSON_THROW(detail::out_of_range::create(402, |
540 | "array index '-' (" + std::to_string(ptr->m_value.array->size()) + |
541 | ") is out of range" )); |
542 | } |
543 | |
544 | // error condition (cf. RFC 6901, Sect. 4) |
545 | if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) |
546 | { |
547 | JSON_THROW(detail::parse_error::create(106, 0, |
548 | "array index '" + reference_token + |
549 | "' must not begin with '0'" )); |
550 | } |
551 | |
552 | // note: at performs range check |
553 | JSON_TRY |
554 | { |
555 | ptr = &ptr->at(static_cast<size_type>(array_index(reference_token))); |
556 | } |
557 | JSON_CATCH(std::invalid_argument&) |
558 | { |
559 | JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number" )); |
560 | } |
561 | break; |
562 | } |
563 | |
564 | default: |
565 | JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'" )); |
566 | } |
567 | } |
568 | |
569 | return *ptr; |
570 | } |
571 | |
572 | /*! |
573 | @brief return a const reference to the pointed to value |
574 | |
575 | @param[in] ptr a JSON value |
576 | |
577 | @return const reference to the JSON value pointed to by the JSON |
578 | pointer |
579 | |
580 | @throw parse_error.106 if an array index begins with '0' |
581 | @throw parse_error.109 if an array index was not a number |
582 | @throw out_of_range.402 if the array index '-' is used |
583 | @throw out_of_range.404 if the JSON pointer can not be resolved |
584 | */ |
585 | const BasicJsonType& get_unchecked(const BasicJsonType* ptr) const |
586 | { |
587 | using size_type = typename BasicJsonType::size_type; |
588 | for (const auto& reference_token : reference_tokens) |
589 | { |
590 | switch (ptr->type()) |
591 | { |
592 | case detail::value_t::object: |
593 | { |
594 | // use unchecked object access |
595 | ptr = &ptr->operator[](reference_token); |
596 | break; |
597 | } |
598 | |
599 | case detail::value_t::array: |
600 | { |
601 | if (JSON_HEDLEY_UNLIKELY(reference_token == "-" )) |
602 | { |
603 | // "-" cannot be used for const access |
604 | JSON_THROW(detail::out_of_range::create(402, |
605 | "array index '-' (" + std::to_string(ptr->m_value.array->size()) + |
606 | ") is out of range" )); |
607 | } |
608 | |
609 | // error condition (cf. RFC 6901, Sect. 4) |
610 | if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) |
611 | { |
612 | JSON_THROW(detail::parse_error::create(106, 0, |
613 | "array index '" + reference_token + |
614 | "' must not begin with '0'" )); |
615 | } |
616 | |
617 | // use unchecked array access |
618 | JSON_TRY |
619 | { |
620 | ptr = &ptr->operator[]( |
621 | static_cast<size_type>(array_index(reference_token))); |
622 | } |
623 | JSON_CATCH(std::invalid_argument&) |
624 | { |
625 | JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number" )); |
626 | } |
627 | break; |
628 | } |
629 | |
630 | default: |
631 | JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'" )); |
632 | } |
633 | } |
634 | |
635 | return *ptr; |
636 | } |
637 | |
638 | /*! |
639 | @throw parse_error.106 if an array index begins with '0' |
640 | @throw parse_error.109 if an array index was not a number |
641 | @throw out_of_range.402 if the array index '-' is used |
642 | @throw out_of_range.404 if the JSON pointer can not be resolved |
643 | */ |
644 | const BasicJsonType& get_checked(const BasicJsonType* ptr) const |
645 | { |
646 | using size_type = typename BasicJsonType::size_type; |
647 | for (const auto& reference_token : reference_tokens) |
648 | { |
649 | switch (ptr->type()) |
650 | { |
651 | case detail::value_t::object: |
652 | { |
653 | // note: at performs range check |
654 | ptr = &ptr->at(reference_token); |
655 | break; |
656 | } |
657 | |
658 | case detail::value_t::array: |
659 | { |
660 | if (JSON_HEDLEY_UNLIKELY(reference_token == "-" )) |
661 | { |
662 | // "-" always fails the range check |
663 | JSON_THROW(detail::out_of_range::create(402, |
664 | "array index '-' (" + std::to_string(ptr->m_value.array->size()) + |
665 | ") is out of range" )); |
666 | } |
667 | |
668 | // error condition (cf. RFC 6901, Sect. 4) |
669 | if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) |
670 | { |
671 | JSON_THROW(detail::parse_error::create(106, 0, |
672 | "array index '" + reference_token + |
673 | "' must not begin with '0'" )); |
674 | } |
675 | |
676 | // note: at performs range check |
677 | JSON_TRY |
678 | { |
679 | ptr = &ptr->at(static_cast<size_type>(array_index(reference_token))); |
680 | } |
681 | JSON_CATCH(std::invalid_argument&) |
682 | { |
683 | JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number" )); |
684 | } |
685 | break; |
686 | } |
687 | |
688 | default: |
689 | JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'" )); |
690 | } |
691 | } |
692 | |
693 | return *ptr; |
694 | } |
695 | |
696 | /*! |
697 | @throw parse_error.106 if an array index begins with '0' |
698 | @throw parse_error.109 if an array index was not a number |
699 | */ |
700 | bool contains(const BasicJsonType* ptr) const |
701 | { |
702 | using size_type = typename BasicJsonType::size_type; |
703 | for (const auto& reference_token : reference_tokens) |
704 | { |
705 | switch (ptr->type()) |
706 | { |
707 | case detail::value_t::object: |
708 | { |
709 | if (not ptr->contains(reference_token)) |
710 | { |
711 | // we did not find the key in the object |
712 | return false; |
713 | } |
714 | |
715 | ptr = &ptr->operator[](reference_token); |
716 | break; |
717 | } |
718 | |
719 | case detail::value_t::array: |
720 | { |
721 | if (JSON_HEDLEY_UNLIKELY(reference_token == "-" )) |
722 | { |
723 | // "-" always fails the range check |
724 | return false; |
725 | } |
726 | |
727 | // error condition (cf. RFC 6901, Sect. 4) |
728 | if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) |
729 | { |
730 | JSON_THROW(detail::parse_error::create(106, 0, |
731 | "array index '" + reference_token + |
732 | "' must not begin with '0'" )); |
733 | } |
734 | |
735 | JSON_TRY |
736 | { |
737 | const auto idx = static_cast<size_type>(array_index(reference_token)); |
738 | if (idx >= ptr->size()) |
739 | { |
740 | // index out of range |
741 | return false; |
742 | } |
743 | |
744 | ptr = &ptr->operator[](idx); |
745 | break; |
746 | } |
747 | JSON_CATCH(std::invalid_argument&) |
748 | { |
749 | JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number" )); |
750 | } |
751 | break; |
752 | } |
753 | |
754 | default: |
755 | { |
756 | // we do not expect primitive values if there is still a |
757 | // reference token to process |
758 | return false; |
759 | } |
760 | } |
761 | } |
762 | |
763 | // no reference token left means we found a primitive value |
764 | return true; |
765 | } |
766 | |
767 | /*! |
768 | @brief split the string input to reference tokens |
769 | |
770 | @note This function is only called by the json_pointer constructor. |
771 | All exceptions below are documented there. |
772 | |
773 | @throw parse_error.107 if the pointer is not empty or begins with '/' |
774 | @throw parse_error.108 if character '~' is not followed by '0' or '1' |
775 | */ |
776 | static std::vector<std::string> split(const std::string& reference_string) |
777 | { |
778 | std::vector<std::string> result; |
779 | |
780 | // special case: empty reference string -> no reference tokens |
781 | if (reference_string.empty()) |
782 | { |
783 | return result; |
784 | } |
785 | |
786 | // check if nonempty reference string begins with slash |
787 | if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/')) |
788 | { |
789 | JSON_THROW(detail::parse_error::create(107, 1, |
790 | "JSON pointer must be empty or begin with '/' - was: '" + |
791 | reference_string + "'" )); |
792 | } |
793 | |
794 | // extract the reference tokens: |
795 | // - slash: position of the last read slash (or end of string) |
796 | // - start: position after the previous slash |
797 | for ( |
798 | // search for the first slash after the first character |
799 | std::size_t slash = reference_string.find_first_of('/', 1), |
800 | // set the beginning of the first reference token |
801 | start = 1; |
802 | // we can stop if start == 0 (if slash == std::string::npos) |
803 | start != 0; |
804 | // set the beginning of the next reference token |
805 | // (will eventually be 0 if slash == std::string::npos) |
806 | start = (slash == std::string::npos) ? 0 : slash + 1, |
807 | // find next slash |
808 | slash = reference_string.find_first_of('/', start)) |
809 | { |
810 | // use the text between the beginning of the reference token |
811 | // (start) and the last slash (slash). |
812 | auto reference_token = reference_string.substr(start, slash - start); |
813 | |
814 | // check reference tokens are properly escaped |
815 | for (std::size_t pos = reference_token.find_first_of('~'); |
816 | pos != std::string::npos; |
817 | pos = reference_token.find_first_of('~', pos + 1)) |
818 | { |
819 | assert(reference_token[pos] == '~'); |
820 | |
821 | // ~ must be followed by 0 or 1 |
822 | if (JSON_HEDLEY_UNLIKELY(pos == reference_token.size() - 1 or |
823 | (reference_token[pos + 1] != '0' and |
824 | reference_token[pos + 1] != '1'))) |
825 | { |
826 | JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'" )); |
827 | } |
828 | } |
829 | |
830 | // finally, store the reference token |
831 | unescape(reference_token); |
832 | result.push_back(reference_token); |
833 | } |
834 | |
835 | return result; |
836 | } |
837 | |
838 | /*! |
839 | @brief replace all occurrences of a substring by another string |
840 | |
841 | @param[in,out] s the string to manipulate; changed so that all |
842 | occurrences of @a f are replaced with @a t |
843 | @param[in] f the substring to replace with @a t |
844 | @param[in] t the string to replace @a f |
845 | |
846 | @pre The search string @a f must not be empty. **This precondition is |
847 | enforced with an assertion.** |
848 | |
849 | @since version 2.0.0 |
850 | */ |
851 | static void replace_substring(std::string& s, const std::string& f, |
852 | const std::string& t) |
853 | { |
854 | assert(not f.empty()); |
855 | for (auto pos = s.find(f); // find first occurrence of f |
856 | pos != std::string::npos; // make sure f was found |
857 | s.replace(pos, f.size(), t), // replace with t, and |
858 | pos = s.find(f, pos + t.size())) // find next occurrence of f |
859 | {} |
860 | } |
861 | |
862 | /// escape "~" to "~0" and "/" to "~1" |
863 | static std::string escape(std::string s) |
864 | { |
865 | replace_substring(s, "~" , "~0" ); |
866 | replace_substring(s, "/" , "~1" ); |
867 | return s; |
868 | } |
869 | |
870 | /// unescape "~1" to tilde and "~0" to slash (order is important!) |
871 | static void unescape(std::string& s) |
872 | { |
873 | replace_substring(s, "~1" , "/" ); |
874 | replace_substring(s, "~0" , "~" ); |
875 | } |
876 | |
877 | /*! |
878 | @param[in] reference_string the reference string to the current value |
879 | @param[in] value the value to consider |
880 | @param[in,out] result the result object to insert values to |
881 | |
882 | @note Empty objects or arrays are flattened to `null`. |
883 | */ |
884 | static void flatten(const std::string& reference_string, |
885 | const BasicJsonType& value, |
886 | BasicJsonType& result) |
887 | { |
888 | switch (value.type()) |
889 | { |
890 | case detail::value_t::array: |
891 | { |
892 | if (value.m_value.array->empty()) |
893 | { |
894 | // flatten empty array as null |
895 | result[reference_string] = nullptr; |
896 | } |
897 | else |
898 | { |
899 | // iterate array and use index as reference string |
900 | for (std::size_t i = 0; i < value.m_value.array->size(); ++i) |
901 | { |
902 | flatten(reference_string + "/" + std::to_string(i), |
903 | value.m_value.array->operator[](i), result); |
904 | } |
905 | } |
906 | break; |
907 | } |
908 | |
909 | case detail::value_t::object: |
910 | { |
911 | if (value.m_value.object->empty()) |
912 | { |
913 | // flatten empty object as null |
914 | result[reference_string] = nullptr; |
915 | } |
916 | else |
917 | { |
918 | // iterate object and use keys as reference string |
919 | for (const auto& element : *value.m_value.object) |
920 | { |
921 | flatten(reference_string + "/" + escape(element.first), element.second, result); |
922 | } |
923 | } |
924 | break; |
925 | } |
926 | |
927 | default: |
928 | { |
929 | // add primitive value with its reference string |
930 | result[reference_string] = value; |
931 | break; |
932 | } |
933 | } |
934 | } |
935 | |
936 | /*! |
937 | @param[in] value flattened JSON |
938 | |
939 | @return unflattened JSON |
940 | |
941 | @throw parse_error.109 if array index is not a number |
942 | @throw type_error.314 if value is not an object |
943 | @throw type_error.315 if object values are not primitive |
944 | @throw type_error.313 if value cannot be unflattened |
945 | */ |
946 | static BasicJsonType |
947 | unflatten(const BasicJsonType& value) |
948 | { |
949 | if (JSON_HEDLEY_UNLIKELY(not value.is_object())) |
950 | { |
951 | JSON_THROW(detail::type_error::create(314, "only objects can be unflattened" )); |
952 | } |
953 | |
954 | BasicJsonType result; |
955 | |
956 | // iterate the JSON object values |
957 | for (const auto& element : *value.m_value.object) |
958 | { |
959 | if (JSON_HEDLEY_UNLIKELY(not element.second.is_primitive())) |
960 | { |
961 | JSON_THROW(detail::type_error::create(315, "values in object must be primitive" )); |
962 | } |
963 | |
964 | // assign value to reference pointed to by JSON pointer; Note that if |
965 | // the JSON pointer is "" (i.e., points to the whole value), function |
966 | // get_and_create returns a reference to result itself. An assignment |
967 | // will then create a primitive value. |
968 | json_pointer(element.first).get_and_create(result) = element.second; |
969 | } |
970 | |
971 | return result; |
972 | } |
973 | |
974 | /*! |
975 | @brief compares two JSON pointers for equality |
976 | |
977 | @param[in] lhs JSON pointer to compare |
978 | @param[in] rhs JSON pointer to compare |
979 | @return whether @a lhs is equal to @a rhs |
980 | |
981 | @complexity Linear in the length of the JSON pointer |
982 | |
983 | @exceptionsafety No-throw guarantee: this function never throws exceptions. |
984 | */ |
985 | friend bool operator==(json_pointer const& lhs, |
986 | json_pointer const& rhs) noexcept |
987 | { |
988 | return lhs.reference_tokens == rhs.reference_tokens; |
989 | } |
990 | |
991 | /*! |
992 | @brief compares two JSON pointers for inequality |
993 | |
994 | @param[in] lhs JSON pointer to compare |
995 | @param[in] rhs JSON pointer to compare |
996 | @return whether @a lhs is not equal @a rhs |
997 | |
998 | @complexity Linear in the length of the JSON pointer |
999 | |
1000 | @exceptionsafety No-throw guarantee: this function never throws exceptions. |
1001 | */ |
1002 | friend bool operator!=(json_pointer const& lhs, |
1003 | json_pointer const& rhs) noexcept |
1004 | { |
1005 | return not (lhs == rhs); |
1006 | } |
1007 | |
1008 | /// the reference tokens |
1009 | std::vector<std::string> reference_tokens; |
1010 | }; |
1011 | } // namespace nlohmann |
1012 | |