1 #pragma once
2 
3 #include <algorithm> // all_of
4 #include <cctype> // isdigit
5 #include <limits> // max
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_idx  array index to append
130     @return JSON pointer with @a array_idx 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_idx)142     json_pointer& operator/=(std::size_t array_idx)
143     {
144         return *this /= std::to_string(array_idx);
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_idx  array index
193     @return a new JSON pointer with @a array_idx 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_idx)203     friend json_pointer operator/(const json_pointer& ptr, std::size_t array_idx)
204     {
205         return json_pointer(ptr) /= array_idx;
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 parse_error.106  if an array index begins with '0'
329     @throw parse_error.109  if an array index begins not with a digit
330     @throw out_of_range.404 if string @a s could not be converted to an integer
331     @throw out_of_range.410 if an array index exceeds size_type
332     */
array_index(const std::string & s)333     static typename BasicJsonType::size_type array_index(const std::string& s)
334     {
335         using size_type = typename BasicJsonType::size_type;
336 
337         // error condition (cf. RFC 6901, Sect. 4)
338         if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && s[0] == '0'))
339         {
340             JSON_THROW(detail::parse_error::create(106, 0,
341                                                    "array index '" + s +
342                                                    "' must not begin with '0'"));
343         }
344 
345         // error condition (cf. RFC 6901, Sect. 4)
346         if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && !(s[0] >= '1' && s[0] <= '9')))
347         {
348             JSON_THROW(detail::parse_error::create(109, 0, "array index '" + s + "' is not a number"));
349         }
350 
351         std::size_t processed_chars = 0;
352         unsigned long long res = 0;
353         JSON_TRY
354         {
355             res = std::stoull(s, &processed_chars);
356         }
357         JSON_CATCH(std::out_of_range&)
358         {
359             JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + s + "'"));
360         }
361 
362         // check if the string was completely read
363         if (JSON_HEDLEY_UNLIKELY(processed_chars != s.size()))
364         {
365             JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + s + "'"));
366         }
367 
368         // only triggered on special platforms (like 32bit), see also
369         // https://github.com/nlohmann/json/pull/2203
370         if (res >= static_cast<unsigned long long>((std::numeric_limits<size_type>::max)()))
371         {
372             JSON_THROW(detail::out_of_range::create(410, "array index " + s + " exceeds size_type")); // LCOV_EXCL_LINE
373         }
374 
375         return static_cast<size_type>(res);
376     }
377 
top() const378     json_pointer top() const
379     {
380         if (JSON_HEDLEY_UNLIKELY(empty()))
381         {
382             JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent"));
383         }
384 
385         json_pointer result = *this;
386         result.reference_tokens = {reference_tokens[0]};
387         return result;
388     }
389 
390     /*!
391     @brief create and return a reference to the pointed to value
392 
393     @complexity Linear in the number of reference tokens.
394 
395     @throw parse_error.109 if array index is not a number
396     @throw type_error.313 if value cannot be unflattened
397     */
get_and_create(BasicJsonType & j) const398     BasicJsonType& get_and_create(BasicJsonType& j) const
399     {
400         auto result = &j;
401 
402         // in case no reference tokens exist, return a reference to the JSON value
403         // j which will be overwritten by a primitive value
404         for (const auto& reference_token : reference_tokens)
405         {
406             switch (result->type())
407             {
408                 case detail::value_t::null:
409                 {
410                     if (reference_token == "0")
411                     {
412                         // start a new array if reference token is 0
413                         result = &result->operator[](0);
414                     }
415                     else
416                     {
417                         // start a new object otherwise
418                         result = &result->operator[](reference_token);
419                     }
420                     break;
421                 }
422 
423                 case detail::value_t::object:
424                 {
425                     // create an entry in the object
426                     result = &result->operator[](reference_token);
427                     break;
428                 }
429 
430                 case detail::value_t::array:
431                 {
432                     // create an entry in the array
433                     result = &result->operator[](array_index(reference_token));
434                     break;
435                 }
436 
437                 /*
438                 The following code is only reached if there exists a reference
439                 token _and_ the current value is primitive. In this case, we have
440                 an error situation, because primitive values may only occur as
441                 single value; that is, with an empty list of reference tokens.
442                 */
443                 default:
444                     JSON_THROW(detail::type_error::create(313, "invalid value to unflatten"));
445             }
446         }
447 
448         return *result;
449     }
450 
451     /*!
452     @brief return a reference to the pointed to value
453 
454     @note This version does not throw if a value is not present, but tries to
455           create nested values instead. For instance, calling this function
456           with pointer `"/this/that"` on a null value is equivalent to calling
457           `operator[]("this").operator[]("that")` on that value, effectively
458           changing the null value to an object.
459 
460     @param[in] ptr  a JSON value
461 
462     @return reference to the JSON value pointed to by the JSON pointer
463 
464     @complexity Linear in the length of the JSON pointer.
465 
466     @throw parse_error.106   if an array index begins with '0'
467     @throw parse_error.109   if an array index was not a number
468     @throw out_of_range.404  if the JSON pointer can not be resolved
469     */
get_unchecked(BasicJsonType * ptr) const470     BasicJsonType& get_unchecked(BasicJsonType* ptr) const
471     {
472         for (const auto& reference_token : reference_tokens)
473         {
474             // convert null values to arrays or objects before continuing
475             if (ptr->is_null())
476             {
477                 // check if reference token is a number
478                 const bool nums =
479                     std::all_of(reference_token.begin(), reference_token.end(),
480                                 [](const unsigned char x)
481                 {
482                     return std::isdigit(x);
483                 });
484 
485                 // change value to array for numbers or "-" or to object otherwise
486                 *ptr = (nums || reference_token == "-")
487                        ? detail::value_t::array
488                        : detail::value_t::object;
489             }
490 
491             switch (ptr->type())
492             {
493                 case detail::value_t::object:
494                 {
495                     // use unchecked object access
496                     ptr = &ptr->operator[](reference_token);
497                     break;
498                 }
499 
500                 case detail::value_t::array:
501                 {
502                     if (reference_token == "-")
503                     {
504                         // explicitly treat "-" as index beyond the end
505                         ptr = &ptr->operator[](ptr->m_value.array->size());
506                     }
507                     else
508                     {
509                         // convert array index to number; unchecked access
510                         ptr = &ptr->operator[](array_index(reference_token));
511                     }
512                     break;
513                 }
514 
515                 default:
516                     JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'"));
517             }
518         }
519 
520         return *ptr;
521     }
522 
523     /*!
524     @throw parse_error.106   if an array index begins with '0'
525     @throw parse_error.109   if an array index was not a number
526     @throw out_of_range.402  if the array index '-' is used
527     @throw out_of_range.404  if the JSON pointer can not be resolved
528     */
get_checked(BasicJsonType * ptr) const529     BasicJsonType& get_checked(BasicJsonType* ptr) const
530     {
531         for (const auto& reference_token : reference_tokens)
532         {
533             switch (ptr->type())
534             {
535                 case detail::value_t::object:
536                 {
537                     // note: at performs range check
538                     ptr = &ptr->at(reference_token);
539                     break;
540                 }
541 
542                 case detail::value_t::array:
543                 {
544                     if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
545                     {
546                         // "-" always fails the range check
547                         JSON_THROW(detail::out_of_range::create(402,
548                                                                 "array index '-' (" + std::to_string(ptr->m_value.array->size()) +
549                                                                 ") is out of range"));
550                     }
551 
552                     // note: at performs range check
553                     ptr = &ptr->at(array_index(reference_token));
554                     break;
555                 }
556 
557                 default:
558                     JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'"));
559             }
560         }
561 
562         return *ptr;
563     }
564 
565     /*!
566     @brief return a const reference to the pointed to value
567 
568     @param[in] ptr  a JSON value
569 
570     @return const reference to the JSON value pointed to by the JSON
571     pointer
572 
573     @throw parse_error.106   if an array index begins with '0'
574     @throw parse_error.109   if an array index was not a number
575     @throw out_of_range.402  if the array index '-' is used
576     @throw out_of_range.404  if the JSON pointer can not be resolved
577     */
get_unchecked(const BasicJsonType * ptr) const578     const BasicJsonType& get_unchecked(const BasicJsonType* ptr) const
579     {
580         for (const auto& reference_token : reference_tokens)
581         {
582             switch (ptr->type())
583             {
584                 case detail::value_t::object:
585                 {
586                     // use unchecked object access
587                     ptr = &ptr->operator[](reference_token);
588                     break;
589                 }
590 
591                 case detail::value_t::array:
592                 {
593                     if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
594                     {
595                         // "-" cannot be used for const access
596                         JSON_THROW(detail::out_of_range::create(402,
597                                                                 "array index '-' (" + std::to_string(ptr->m_value.array->size()) +
598                                                                 ") is out of range"));
599                     }
600 
601                     // use unchecked array access
602                     ptr = &ptr->operator[](array_index(reference_token));
603                     break;
604                 }
605 
606                 default:
607                     JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'"));
608             }
609         }
610 
611         return *ptr;
612     }
613 
614     /*!
615     @throw parse_error.106   if an array index begins with '0'
616     @throw parse_error.109   if an array index was not a number
617     @throw out_of_range.402  if the array index '-' is used
618     @throw out_of_range.404  if the JSON pointer can not be resolved
619     */
get_checked(const BasicJsonType * ptr) const620     const BasicJsonType& get_checked(const BasicJsonType* ptr) const
621     {
622         for (const auto& reference_token : reference_tokens)
623         {
624             switch (ptr->type())
625             {
626                 case detail::value_t::object:
627                 {
628                     // note: at performs range check
629                     ptr = &ptr->at(reference_token);
630                     break;
631                 }
632 
633                 case detail::value_t::array:
634                 {
635                     if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
636                     {
637                         // "-" always fails the range check
638                         JSON_THROW(detail::out_of_range::create(402,
639                                                                 "array index '-' (" + std::to_string(ptr->m_value.array->size()) +
640                                                                 ") is out of range"));
641                     }
642 
643                     // note: at performs range check
644                     ptr = &ptr->at(array_index(reference_token));
645                     break;
646                 }
647 
648                 default:
649                     JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'"));
650             }
651         }
652 
653         return *ptr;
654     }
655 
656     /*!
657     @throw parse_error.106   if an array index begins with '0'
658     @throw parse_error.109   if an array index was not a number
659     */
contains(const BasicJsonType * ptr) const660     bool contains(const BasicJsonType* ptr) const
661     {
662         for (const auto& reference_token : reference_tokens)
663         {
664             switch (ptr->type())
665             {
666                 case detail::value_t::object:
667                 {
668                     if (!ptr->contains(reference_token))
669                     {
670                         // we did not find the key in the object
671                         return false;
672                     }
673 
674                     ptr = &ptr->operator[](reference_token);
675                     break;
676                 }
677 
678                 case detail::value_t::array:
679                 {
680                     if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
681                     {
682                         // "-" always fails the range check
683                         return false;
684                     }
685                     if (JSON_HEDLEY_UNLIKELY(reference_token.size() == 1 && !("0" <= reference_token && reference_token <= "9")))
686                     {
687                         // invalid char
688                         return false;
689                     }
690                     if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1))
691                     {
692                         if (JSON_HEDLEY_UNLIKELY(!('1' <= reference_token[0] && reference_token[0] <= '9')))
693                         {
694                             // first char should be between '1' and '9'
695                             return false;
696                         }
697                         for (std::size_t i = 1; i < reference_token.size(); i++)
698                         {
699                             if (JSON_HEDLEY_UNLIKELY(!('0' <= reference_token[i] && reference_token[i] <= '9')))
700                             {
701                                 // other char should be between '0' and '9'
702                                 return false;
703                             }
704                         }
705                     }
706 
707                     const auto idx = array_index(reference_token);
708                     if (idx >= ptr->size())
709                     {
710                         // index out of range
711                         return false;
712                     }
713 
714                     ptr = &ptr->operator[](idx);
715                     break;
716                 }
717 
718                 default:
719                 {
720                     // we do not expect primitive values if there is still a
721                     // reference token to process
722                     return false;
723                 }
724             }
725         }
726 
727         // no reference token left means we found a primitive value
728         return true;
729     }
730 
731     /*!
732     @brief split the string input to reference tokens
733 
734     @note This function is only called by the json_pointer constructor.
735           All exceptions below are documented there.
736 
737     @throw parse_error.107  if the pointer is not empty or begins with '/'
738     @throw parse_error.108  if character '~' is not followed by '0' or '1'
739     */
split(const std::string & reference_string)740     static std::vector<std::string> split(const std::string& reference_string)
741     {
742         std::vector<std::string> result;
743 
744         // special case: empty reference string -> no reference tokens
745         if (reference_string.empty())
746         {
747             return result;
748         }
749 
750         // check if nonempty reference string begins with slash
751         if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/'))
752         {
753             JSON_THROW(detail::parse_error::create(107, 1,
754                                                    "JSON pointer must be empty or begin with '/' - was: '" +
755                                                    reference_string + "'"));
756         }
757 
758         // extract the reference tokens:
759         // - slash: position of the last read slash (or end of string)
760         // - start: position after the previous slash
761         for (
762             // search for the first slash after the first character
763             std::size_t slash = reference_string.find_first_of('/', 1),
764             // set the beginning of the first reference token
765             start = 1;
766             // we can stop if start == 0 (if slash == std::string::npos)
767             start != 0;
768             // set the beginning of the next reference token
769             // (will eventually be 0 if slash == std::string::npos)
770             start = (slash == std::string::npos) ? 0 : slash + 1,
771             // find next slash
772             slash = reference_string.find_first_of('/', start))
773         {
774             // use the text between the beginning of the reference token
775             // (start) and the last slash (slash).
776             auto reference_token = reference_string.substr(start, slash - start);
777 
778             // check reference tokens are properly escaped
779             for (std::size_t pos = reference_token.find_first_of('~');
780                     pos != std::string::npos;
781                     pos = reference_token.find_first_of('~', pos + 1))
782             {
783                 JSON_ASSERT(reference_token[pos] == '~');
784 
785                 // ~ must be followed by 0 or 1
786                 if (JSON_HEDLEY_UNLIKELY(pos == reference_token.size() - 1 ||
787                                          (reference_token[pos + 1] != '0' &&
788                                           reference_token[pos + 1] != '1')))
789                 {
790                     JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'"));
791                 }
792             }
793 
794             // finally, store the reference token
795             unescape(reference_token);
796             result.push_back(reference_token);
797         }
798 
799         return result;
800     }
801 
802     /*!
803     @brief replace all occurrences of a substring by another string
804 
805     @param[in,out] s  the string to manipulate; changed so that all
806                    occurrences of @a f are replaced with @a t
807     @param[in]     f  the substring to replace with @a t
808     @param[in]     t  the string to replace @a f
809 
810     @pre The search string @a f must not be empty. **This precondition is
811     enforced with an assertion.**
812 
813     @since version 2.0.0
814     */
replace_substring(std::string & s,const std::string & f,const std::string & t)815     static void replace_substring(std::string& s, const std::string& f,
816                                   const std::string& t)
817     {
818         JSON_ASSERT(!f.empty());
819         for (auto pos = s.find(f);                // find first occurrence of f
820                 pos != std::string::npos;         // make sure f was found
821                 s.replace(pos, f.size(), t),      // replace with t, and
822                 pos = s.find(f, pos + t.size()))  // find next occurrence of f
823         {}
824     }
825 
826     /// escape "~" to "~0" and "/" to "~1"
escape(std::string s)827     static std::string escape(std::string s)
828     {
829         replace_substring(s, "~", "~0");
830         replace_substring(s, "/", "~1");
831         return s;
832     }
833 
834     /// unescape "~1" to tilde and "~0" to slash (order is important!)
unescape(std::string & s)835     static void unescape(std::string& s)
836     {
837         replace_substring(s, "~1", "/");
838         replace_substring(s, "~0", "~");
839     }
840 
841     /*!
842     @param[in] reference_string  the reference string to the current value
843     @param[in] value             the value to consider
844     @param[in,out] result        the result object to insert values to
845 
846     @note Empty objects or arrays are flattened to `null`.
847     */
flatten(const std::string & reference_string,const BasicJsonType & value,BasicJsonType & result)848     static void flatten(const std::string& reference_string,
849                         const BasicJsonType& value,
850                         BasicJsonType& result)
851     {
852         switch (value.type())
853         {
854             case detail::value_t::array:
855             {
856                 if (value.m_value.array->empty())
857                 {
858                     // flatten empty array as null
859                     result[reference_string] = nullptr;
860                 }
861                 else
862                 {
863                     // iterate array and use index as reference string
864                     for (std::size_t i = 0; i < value.m_value.array->size(); ++i)
865                     {
866                         flatten(reference_string + "/" + std::to_string(i),
867                                 value.m_value.array->operator[](i), result);
868                     }
869                 }
870                 break;
871             }
872 
873             case detail::value_t::object:
874             {
875                 if (value.m_value.object->empty())
876                 {
877                     // flatten empty object as null
878                     result[reference_string] = nullptr;
879                 }
880                 else
881                 {
882                     // iterate object and use keys as reference string
883                     for (const auto& element : *value.m_value.object)
884                     {
885                         flatten(reference_string + "/" + escape(element.first), element.second, result);
886                     }
887                 }
888                 break;
889             }
890 
891             default:
892             {
893                 // add primitive value with its reference string
894                 result[reference_string] = value;
895                 break;
896             }
897         }
898     }
899 
900     /*!
901     @param[in] value  flattened JSON
902 
903     @return unflattened JSON
904 
905     @throw parse_error.109 if array index is not a number
906     @throw type_error.314  if value is not an object
907     @throw type_error.315  if object values are not primitive
908     @throw type_error.313  if value cannot be unflattened
909     */
910     static BasicJsonType
unflatten(const BasicJsonType & value)911     unflatten(const BasicJsonType& value)
912     {
913         if (JSON_HEDLEY_UNLIKELY(!value.is_object()))
914         {
915             JSON_THROW(detail::type_error::create(314, "only objects can be unflattened"));
916         }
917 
918         BasicJsonType result;
919 
920         // iterate the JSON object values
921         for (const auto& element : *value.m_value.object)
922         {
923             if (JSON_HEDLEY_UNLIKELY(!element.second.is_primitive()))
924             {
925                 JSON_THROW(detail::type_error::create(315, "values in object must be primitive"));
926             }
927 
928             // assign value to reference pointed to by JSON pointer; Note that if
929             // the JSON pointer is "" (i.e., points to the whole value), function
930             // get_and_create returns a reference to result itself. An assignment
931             // will then create a primitive value.
932             json_pointer(element.first).get_and_create(result) = element.second;
933         }
934 
935         return result;
936     }
937 
938     /*!
939     @brief compares two JSON pointers for equality
940 
941     @param[in] lhs  JSON pointer to compare
942     @param[in] rhs  JSON pointer to compare
943     @return whether @a lhs is equal to @a rhs
944 
945     @complexity Linear in the length of the JSON pointer
946 
947     @exceptionsafety No-throw guarantee: this function never throws exceptions.
948     */
operator ==(json_pointer const & lhs,json_pointer const & rhs)949     friend bool operator==(json_pointer const& lhs,
950                            json_pointer const& rhs) noexcept
951     {
952         return lhs.reference_tokens == rhs.reference_tokens;
953     }
954 
955     /*!
956     @brief compares two JSON pointers for inequality
957 
958     @param[in] lhs  JSON pointer to compare
959     @param[in] rhs  JSON pointer to compare
960     @return whether @a lhs is not equal @a rhs
961 
962     @complexity Linear in the length of the JSON pointer
963 
964     @exceptionsafety No-throw guarantee: this function never throws exceptions.
965     */
operator !=(json_pointer const & lhs,json_pointer const & rhs)966     friend bool operator!=(json_pointer const& lhs,
967                            json_pointer const& rhs) noexcept
968     {
969         return !(lhs == rhs);
970     }
971 
972     /// the reference tokens
973     std::vector<std::string> reference_tokens;
974 };
975 }  // namespace nlohmann
976