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
15namespace nlohmann
16{
17template<typename BasicJsonType>
18class 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