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     */
json_pointer(const std::string & s="")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     */
to_string() const64     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()
operator std::string() const75     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     */
operator /=(const json_pointer & ptr)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     */
operator /=(std::string token)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     */
operator /=(std::size_t array_index)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     */
operator /(const json_pointer & lhs,const json_pointer & rhs)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     */
operator /(const json_pointer & ptr,std::string token)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     */
operator /(const json_pointer & ptr,std::size_t array_index)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     */
parent_pointer() const221     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     */
pop_back()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     */
back() const270     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     */
push_back(const std::string & token)292     void push_back(const std::string& token)
293     {
294         reference_tokens.push_back(token);
295     }
296 
297     /// @copydoc push_back(const std::string&)
push_back(std::string && token)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     */
empty() const317     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     */
array_index(const std::string & s)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 
top() const344     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     */
get_and_create(BasicJsonType & j) const364     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     */
get_unchecked(BasicJsonType * ptr) const444     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     */
get_checked(BasicJsonType * ptr) const520     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     */
get_unchecked(const BasicJsonType * ptr) const585     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     */
get_checked(const BasicJsonType * ptr) const644     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     */
contains(const BasicJsonType * ptr) const700     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     */
split(const std::string & reference_string)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     */
replace_substring(std::string & s,const std::string & f,const std::string & t)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"
escape(std::string s)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!)
unescape(std::string & s)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     */
flatten(const std::string & reference_string,const BasicJsonType & value,BasicJsonType & result)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
unflatten(const BasicJsonType & value)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     */
operator ==(json_pointer const & lhs,json_pointer const & rhs)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     */
operator !=(json_pointer const & lhs,json_pointer const & rhs)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