Horizon
Loading...
Searching...
No Matches
json_pointer.hpp
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/string_escape.hpp>
14#include <nlohmann/detail/value_t.hpp>
15
16namespace nlohmann
17{
18template<typename BasicJsonType>
20{
21 // allow basic_json to access private members
22 NLOHMANN_BASIC_JSON_TPL_DECLARATION
23 friend class basic_json;
24
25 public:
47 explicit json_pointer(const std::string& s = "")
48 : reference_tokens(split(s))
49 {}
50
65 std::string to_string() const
66 {
67 return std::accumulate(reference_tokens.begin(), reference_tokens.end(),
68 std::string{},
69 [](const std::string & a, const std::string & b)
70 {
71 return a + "/" + detail::escape(b);
72 });
73 }
74
76 operator std::string() const
77 {
78 return to_string();
79 }
80
98 {
99 reference_tokens.insert(reference_tokens.end(),
100 ptr.reference_tokens.begin(),
101 ptr.reference_tokens.end());
102 return *this;
103 }
104
121 json_pointer& operator/=(std::string token)
122 {
123 push_back(std::move(token));
124 return *this;
125 }
126
143 json_pointer& operator/=(std::size_t array_idx)
144 {
145 return *this /= std::to_string(array_idx);
146 }
147
164 const json_pointer& rhs)
165 {
166 return json_pointer(lhs) /= rhs;
167 }
168
184 friend json_pointer operator/(const json_pointer& ptr, std::string token) // NOLINT(performance-unnecessary-value-param)
185 {
186 return json_pointer(ptr) /= std::move(token);
187 }
188
204 friend json_pointer operator/(const json_pointer& ptr, std::size_t array_idx)
205 {
206 return json_pointer(ptr) /= array_idx;
207 }
208
223 {
224 if (empty())
225 {
226 return *this;
227 }
228
229 json_pointer res = *this;
230 res.pop_back();
231 return res;
232 }
233
247 void pop_back()
248 {
249 if (JSON_HEDLEY_UNLIKELY(empty()))
250 {
251 JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", BasicJsonType()));
252 }
253
254 reference_tokens.pop_back();
255 }
256
271 const std::string& back() const
272 {
273 if (JSON_HEDLEY_UNLIKELY(empty()))
274 {
275 JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", BasicJsonType()));
276 }
277
278 return reference_tokens.back();
279 }
280
293 void push_back(const std::string& token)
294 {
295 reference_tokens.push_back(token);
296 }
297
299 void push_back(std::string&& token)
300 {
301 reference_tokens.push_back(std::move(token));
302 }
303
318 bool empty() const noexcept
319 {
320 return reference_tokens.empty();
321 }
322
323 private:
334 static typename BasicJsonType::size_type array_index(const std::string& s)
335 {
336 using size_type = typename BasicJsonType::size_type;
337
338 // error condition (cf. RFC 6901, Sect. 4)
339 if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && s[0] == '0'))
340 {
341 JSON_THROW(detail::parse_error::create(106, 0, "array index '" + s + "' must not begin with '0'", BasicJsonType()));
342 }
343
344 // error condition (cf. RFC 6901, Sect. 4)
345 if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && !(s[0] >= '1' && s[0] <= '9')))
346 {
347 JSON_THROW(detail::parse_error::create(109, 0, "array index '" + s + "' is not a number", BasicJsonType()));
348 }
349
350 std::size_t processed_chars = 0;
351 unsigned long long res = 0; // NOLINT(runtime/int)
352 JSON_TRY
353 {
354 res = std::stoull(s, &processed_chars);
355 }
356 JSON_CATCH(std::out_of_range&)
357 {
358 JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + s + "'", BasicJsonType()));
359 }
360
361 // check if the string was completely read
362 if (JSON_HEDLEY_UNLIKELY(processed_chars != s.size()))
363 {
364 JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + s + "'", BasicJsonType()));
365 }
366
367 // only triggered on special platforms (like 32bit), see also
368 // https://github.com/nlohmann/json/pull/2203
369 if (res >= static_cast<unsigned long long>((std::numeric_limits<size_type>::max)())) // NOLINT(runtime/int)
370 {
371 JSON_THROW(detail::out_of_range::create(410, "array index " + s + " exceeds size_type", BasicJsonType())); // LCOV_EXCL_LINE
372 }
373
374 return static_cast<size_type>(res);
375 }
376
377 JSON_PRIVATE_UNLESS_TESTED:
378 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", BasicJsonType()));
383 }
384
385 json_pointer result = *this;
386 result.reference_tokens = {reference_tokens[0]};
387 return result;
388 }
389
390 private:
399 BasicJsonType& get_and_create(BasicJsonType& j) const
400 {
401 auto* result = &j;
402
403 // in case no reference tokens exist, return a reference to the JSON value
404 // j which will be overwritten by a primitive value
405 for (const auto& reference_token : reference_tokens)
406 {
407 switch (result->type())
408 {
410 {
411 if (reference_token == "0")
412 {
413 // start a new array if reference token is 0
414 result = &result->operator[](0);
415 }
416 else
417 {
418 // start a new object otherwise
419 result = &result->operator[](reference_token);
420 }
421 break;
422 }
423
425 {
426 // create an entry in the object
427 result = &result->operator[](reference_token);
428 break;
429 }
430
432 {
433 // create an entry in the array
434 result = &result->operator[](array_index(reference_token));
435 break;
436 }
437
438 /*
439 The following code is only reached if there exists a reference
440 token _and_ the current value is primitive. In this case, we have
441 an error situation, because primitive values may only occur as
442 single value; that is, with an empty list of reference tokens.
443 */
451 default:
452 JSON_THROW(detail::type_error::create(313, "invalid value to unflatten", j));
453 }
454 }
455
456 return *result;
457 }
458
478 BasicJsonType& get_unchecked(BasicJsonType* ptr) const
479 {
480 for (const auto& reference_token : reference_tokens)
481 {
482 // convert null values to arrays or objects before continuing
483 if (ptr->is_null())
484 {
485 // check if reference token is a number
486 const bool nums =
487 std::all_of(reference_token.begin(), reference_token.end(),
488 [](const unsigned char x)
489 {
490 return std::isdigit(x);
491 });
492
493 // change value to array for numbers or "-" or to object otherwise
494 *ptr = (nums || reference_token == "-")
496 : detail::value_t::object;
497 }
498
499 switch (ptr->type())
500 {
502 {
503 // use unchecked object access
504 ptr = &ptr->operator[](reference_token);
505 break;
506 }
507
509 {
510 if (reference_token == "-")
511 {
512 // explicitly treat "-" as index beyond the end
513 ptr = &ptr->operator[](ptr->m_value.array->size());
514 }
515 else
516 {
517 // convert array index to number; unchecked access
518 ptr = &ptr->operator[](array_index(reference_token));
519 }
520 break;
521 }
522
531 default:
532 JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'", *ptr));
533 }
534 }
535
536 return *ptr;
537 }
538
545 BasicJsonType& get_checked(BasicJsonType* ptr) const
546 {
547 for (const auto& reference_token : reference_tokens)
548 {
549 switch (ptr->type())
550 {
552 {
553 // note: at performs range check
554 ptr = &ptr->at(reference_token);
555 break;
556 }
557
559 {
560 if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
561 {
562 // "-" always fails the range check
563 JSON_THROW(detail::out_of_range::create(402,
564 "array index '-' (" + std::to_string(ptr->m_value.array->size()) +
565 ") is out of range", *ptr));
566 }
567
568 // note: at performs range check
569 ptr = &ptr->at(array_index(reference_token));
570 break;
571 }
572
581 default:
582 JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'", *ptr));
583 }
584 }
585
586 return *ptr;
587 }
588
602 const BasicJsonType& get_unchecked(const BasicJsonType* ptr) const
603 {
604 for (const auto& reference_token : reference_tokens)
605 {
606 switch (ptr->type())
607 {
609 {
610 // use unchecked object access
611 ptr = &ptr->operator[](reference_token);
612 break;
613 }
614
616 {
617 if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
618 {
619 // "-" cannot be used for const access
620 JSON_THROW(detail::out_of_range::create(402, "array index '-' (" + std::to_string(ptr->m_value.array->size()) + ") is out of range", *ptr));
621 }
622
623 // use unchecked array access
624 ptr = &ptr->operator[](array_index(reference_token));
625 break;
626 }
627
636 default:
637 JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'", *ptr));
638 }
639 }
640
641 return *ptr;
642 }
643
650 const BasicJsonType& get_checked(const BasicJsonType* ptr) const
651 {
652 for (const auto& reference_token : reference_tokens)
653 {
654 switch (ptr->type())
655 {
657 {
658 // note: at performs range check
659 ptr = &ptr->at(reference_token);
660 break;
661 }
662
664 {
665 if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
666 {
667 // "-" always fails the range check
668 JSON_THROW(detail::out_of_range::create(402,
669 "array index '-' (" + std::to_string(ptr->m_value.array->size()) +
670 ") is out of range", *ptr));
671 }
672
673 // note: at performs range check
674 ptr = &ptr->at(array_index(reference_token));
675 break;
676 }
677
686 default:
687 JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'", *ptr));
688 }
689 }
690
691 return *ptr;
692 }
693
698 bool contains(const BasicJsonType* ptr) const
699 {
700 for (const auto& reference_token : reference_tokens)
701 {
702 switch (ptr->type())
703 {
705 {
706 if (!ptr->contains(reference_token))
707 {
708 // we did not find the key in the object
709 return false;
710 }
711
712 ptr = &ptr->operator[](reference_token);
713 break;
714 }
715
717 {
718 if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
719 {
720 // "-" always fails the range check
721 return false;
722 }
723 if (JSON_HEDLEY_UNLIKELY(reference_token.size() == 1 && !("0" <= reference_token && reference_token <= "9")))
724 {
725 // invalid char
726 return false;
727 }
728 if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1))
729 {
730 if (JSON_HEDLEY_UNLIKELY(!('1' <= reference_token[0] && reference_token[0] <= '9')))
731 {
732 // first char should be between '1' and '9'
733 return false;
734 }
735 for (std::size_t i = 1; i < reference_token.size(); i++)
736 {
737 if (JSON_HEDLEY_UNLIKELY(!('0' <= reference_token[i] && reference_token[i] <= '9')))
738 {
739 // other char should be between '0' and '9'
740 return false;
741 }
742 }
743 }
744
745 const auto idx = array_index(reference_token);
746 if (idx >= ptr->size())
747 {
748 // index out of range
749 return false;
750 }
751
752 ptr = &ptr->operator[](idx);
753 break;
754 }
755
764 default:
765 {
766 // we do not expect primitive values if there is still a
767 // reference token to process
768 return false;
769 }
770 }
771 }
772
773 // no reference token left means we found a primitive value
774 return true;
775 }
776
786 static std::vector<std::string> split(const std::string& reference_string)
787 {
788 std::vector<std::string> result;
789
790 // special case: empty reference string -> no reference tokens
791 if (reference_string.empty())
792 {
793 return result;
794 }
795
796 // check if nonempty reference string begins with slash
797 if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/'))
798 {
799 JSON_THROW(detail::parse_error::create(107, 1, "JSON pointer must be empty or begin with '/' - was: '" + reference_string + "'", BasicJsonType()));
800 }
801
802 // extract the reference tokens:
803 // - slash: position of the last read slash (or end of string)
804 // - start: position after the previous slash
805 for (
806 // search for the first slash after the first character
807 std::size_t slash = reference_string.find_first_of('/', 1),
808 // set the beginning of the first reference token
809 start = 1;
810 // we can stop if start == 0 (if slash == std::string::npos)
811 start != 0;
812 // set the beginning of the next reference token
813 // (will eventually be 0 if slash == std::string::npos)
814 start = (slash == std::string::npos) ? 0 : slash + 1,
815 // find next slash
816 slash = reference_string.find_first_of('/', start))
817 {
818 // use the text between the beginning of the reference token
819 // (start) and the last slash (slash).
820 auto reference_token = reference_string.substr(start, slash - start);
821
822 // check reference tokens are properly escaped
823 for (std::size_t pos = reference_token.find_first_of('~');
824 pos != std::string::npos;
825 pos = reference_token.find_first_of('~', pos + 1))
826 {
827 JSON_ASSERT(reference_token[pos] == '~');
828
829 // ~ must be followed by 0 or 1
830 if (JSON_HEDLEY_UNLIKELY(pos == reference_token.size() - 1 ||
831 (reference_token[pos + 1] != '0' &&
832 reference_token[pos + 1] != '1')))
833 {
834 JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'", BasicJsonType()));
835 }
836 }
837
838 // finally, store the reference token
839 detail::unescape(reference_token);
840 result.push_back(reference_token);
841 }
842
843 return result;
844 }
845
846 private:
854 static void flatten(const std::string& reference_string,
855 const BasicJsonType& value,
856 BasicJsonType& result)
857 {
858 switch (value.type())
859 {
861 {
862 if (value.m_value.array->empty())
863 {
864 // flatten empty array as null
865 result[reference_string] = nullptr;
866 }
867 else
868 {
869 // iterate array and use index as reference string
870 for (std::size_t i = 0; i < value.m_value.array->size(); ++i)
871 {
872 flatten(reference_string + "/" + std::to_string(i),
873 value.m_value.array->operator[](i), result);
874 }
875 }
876 break;
877 }
878
880 {
881 if (value.m_value.object->empty())
882 {
883 // flatten empty object as null
884 result[reference_string] = nullptr;
885 }
886 else
887 {
888 // iterate object and use keys as reference string
889 for (const auto& element : *value.m_value.object)
890 {
891 flatten(reference_string + "/" + detail::escape(element.first), element.second, result);
892 }
893 }
894 break;
895 }
896
905 default:
906 {
907 // add primitive value with its reference string
908 result[reference_string] = value;
909 break;
910 }
911 }
912 }
913
924 static BasicJsonType
925 unflatten(const BasicJsonType& value)
926 {
927 if (JSON_HEDLEY_UNLIKELY(!value.is_object()))
928 {
929 JSON_THROW(detail::type_error::create(314, "only objects can be unflattened", value));
930 }
931
932 BasicJsonType result;
933
934 // iterate the JSON object values
935 for (const auto& element : *value.m_value.object)
936 {
937 if (JSON_HEDLEY_UNLIKELY(!element.second.is_primitive()))
938 {
939 JSON_THROW(detail::type_error::create(315, "values in object must be primitive", element.second));
940 }
941
942 // assign value to reference pointed to by JSON pointer; Note that if
943 // the JSON pointer is "" (i.e., points to the whole value), function
944 // get_and_create returns a reference to result itself. An assignment
945 // will then create a primitive value.
946 json_pointer(element.first).get_and_create(result) = element.second;
947 }
948
949 return result;
950 }
951
963 friend bool operator==(json_pointer const& lhs,
964 json_pointer const& rhs) noexcept
965 {
966 return lhs.reference_tokens == rhs.reference_tokens;
967 }
968
980 friend bool operator!=(json_pointer const& lhs,
981 json_pointer const& rhs) noexcept
982 {
983 return !(lhs == rhs);
984 }
985
987 std::vector<std::string> reference_tokens;
988};
989} // namespace nlohmann
a class to store JSON values
Definition json.hpp:177
static parse_error create(int id_, const position_t &pos, const std::string &what_arg, const BasicJsonType &context)
create a parse error exception
Definition exceptions.hpp:197
JSON Pointer.
Definition json_pointer.hpp:20
json_pointer & operator/=(std::string token)
append an unescaped reference token at the end of this JSON pointer
Definition json_pointer.hpp:121
json_pointer & operator/=(const json_pointer &ptr)
append another JSON pointer at the end of this JSON pointer
Definition json_pointer.hpp:97
std::string to_string() const
return a string representation of the JSON pointer
Definition json_pointer.hpp:65
friend bool operator==(json_pointer const &lhs, json_pointer const &rhs) noexcept
compares two JSON pointers for equality
Definition json_pointer.hpp:963
void pop_back()
remove last reference token
Definition json_pointer.hpp:247
const std::string & back() const
return last reference token
Definition json_pointer.hpp:271
bool empty() const noexcept
return whether pointer points to the root document
Definition json_pointer.hpp:318
friend bool operator!=(json_pointer const &lhs, json_pointer const &rhs) noexcept
compares two JSON pointers for inequality
Definition json_pointer.hpp:980
void push_back(const std::string &token)
append an unescaped token at the end of the reference pointer
Definition json_pointer.hpp:293
json_pointer(const std::string &s="")
create JSON pointer
Definition json_pointer.hpp:47
friend json_pointer operator/(const json_pointer &lhs, const json_pointer &rhs)
create a new JSON pointer by appending the right JSON pointer at the end of the left JSON pointer
Definition json_pointer.hpp:163
friend json_pointer operator/(const json_pointer &ptr, std::string token)
create a new JSON pointer by appending the unescaped token at the end of the JSON pointer
Definition json_pointer.hpp:184
void push_back(std::string &&token)
append an unescaped token at the end of the reference pointer
Definition json_pointer.hpp:299
json_pointer & operator/=(std::size_t array_idx)
append an array index at the end of this JSON pointer
Definition json_pointer.hpp:143
friend json_pointer operator/(const json_pointer &ptr, std::size_t array_idx)
create a new JSON pointer by appending the array-index-token at the end of the JSON pointer
Definition json_pointer.hpp:204
json_pointer parent_pointer() const
returns the parent of this JSON pointer
Definition json_pointer.hpp:222
value_t
the JSON type enumeration
Definition value_t.hpp:41
@ number_integer
number value (signed integer)
@ discarded
discarded by the parser callback function
@ binary
binary array (ordered collection of bytes)
@ object
object (unordered set of name/value pairs)
@ number_float
number value (floating-point)
@ number_unsigned
number value (unsigned integer)
@ array
array (ordered collection of values)
std::string escape(std::string s)
string escaping as described in RFC 6901 (Sect. 4)
Definition string_escape.hpp:42
namespace for Niels Lohmann
Definition adl_serializer.hpp:12