Horizon
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/value_t.hpp>
14 
15 namespace nlohmann
16 {
17 template<typename BasicJsonType>
19 {
20  // allow basic_json to access private members
21  NLOHMANN_BASIC_JSON_TPL_DECLARATION
22  friend class basic_json;
23 
24  public:
46  explicit json_pointer(const std::string& s = "")
47  : reference_tokens(split(s))
48  {}
49 
64  std::string to_string() const
65  {
66  return std::accumulate(reference_tokens.begin(), reference_tokens.end(),
67  std::string{},
68  [](const std::string & a, const std::string & b)
69  {
70  return a + "/" + escape(b);
71  });
72  }
73 
75  operator std::string() const
76  {
77  return to_string();
78  }
79 
97  {
98  reference_tokens.insert(reference_tokens.end(),
99  ptr.reference_tokens.begin(),
100  ptr.reference_tokens.end());
101  return *this;
102  }
103 
120  json_pointer& operator/=(std::string token)
121  {
122  push_back(std::move(token));
123  return *this;
124  }
125 
142  json_pointer& operator/=(std::size_t array_idx)
143  {
144  return *this /= std::to_string(array_idx);
145  }
146 
163  const json_pointer& rhs)
164  {
165  return json_pointer(lhs) /= rhs;
166  }
167 
183  friend json_pointer operator/(const json_pointer& ptr, std::string token)
184  {
185  return json_pointer(ptr) /= std::move(token);
186  }
187 
203  friend json_pointer operator/(const json_pointer& ptr, std::size_t array_idx)
204  {
205  return json_pointer(ptr) /= array_idx;
206  }
207 
222  {
223  if (empty())
224  {
225  return *this;
226  }
227 
228  json_pointer res = *this;
229  res.pop_back();
230  return res;
231  }
232 
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 
270  const std::string& back() const
271  {
272  if (JSON_HEDLEY_UNLIKELY(empty()))
273  {
274  JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent"));
275  }
276 
277  return reference_tokens.back();
278  }
279 
292  void push_back(const std::string& token)
293  {
294  reference_tokens.push_back(token);
295  }
296 
298  void push_back(std::string&& token)
299  {
300  reference_tokens.push_back(std::move(token));
301  }
302 
317  bool empty() const noexcept
318  {
319  return reference_tokens.empty();
320  }
321 
322  private:
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 
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"));
383  }
384 
385  json_pointer result = *this;
386  result.reference_tokens = {reference_tokens[0]};
387  return result;
388  }
389 
398  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  {
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 
424  {
425  // create an entry in the object
426  result = &result->operator[](reference_token);
427  break;
428  }
429 
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 
470  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 == "-")
489  }
490 
491  switch (ptr->type())
492  {
494  {
495  // use unchecked object access
496  ptr = &ptr->operator[](reference_token);
497  break;
498  }
499 
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 
529  BasicJsonType& get_checked(BasicJsonType* ptr) const
530  {
531  for (const auto& reference_token : reference_tokens)
532  {
533  switch (ptr->type())
534  {
536  {
537  // note: at performs range check
538  ptr = &ptr->at(reference_token);
539  break;
540  }
541 
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 
578  const BasicJsonType& get_unchecked(const BasicJsonType* ptr) const
579  {
580  for (const auto& reference_token : reference_tokens)
581  {
582  switch (ptr->type())
583  {
585  {
586  // use unchecked object access
587  ptr = &ptr->operator[](reference_token);
588  break;
589  }
590 
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 
620  const BasicJsonType& get_checked(const BasicJsonType* ptr) const
621  {
622  for (const auto& reference_token : reference_tokens)
623  {
624  switch (ptr->type())
625  {
627  {
628  // note: at performs range check
629  ptr = &ptr->at(reference_token);
630  break;
631  }
632 
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 
660  bool contains(const BasicJsonType* ptr) const
661  {
662  for (const auto& reference_token : reference_tokens)
663  {
664  switch (ptr->type())
665  {
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 
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 
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 
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 
827  static std::string escape(std::string s)
828  {
829  replace_substring(s, "~", "~0");
830  replace_substring(s, "/", "~1");
831  return s;
832  }
833 
835  static void unescape(std::string& s)
836  {
837  replace_substring(s, "~1", "/");
838  replace_substring(s, "~0", "~");
839  }
840 
848  static void flatten(const std::string& reference_string,
849  const BasicJsonType& value,
850  BasicJsonType& result)
851  {
852  switch (value.type())
853  {
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 
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 
910  static BasicJsonType
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 
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 
966  friend bool operator!=(json_pointer const& lhs,
967  json_pointer const& rhs) noexcept
968  {
969  return !(lhs == rhs);
970  }
971 
973  std::vector<std::string> reference_tokens;
974 };
975 } // namespace nlohmann
a class to store JSON values
Definition: json.hpp:170
static parse_error create(int id_, const position_t &pos, const std::string &what_arg)
create a parse error exception
Definition: exceptions.hpp:130
JSON Pointer.
Definition: json_pointer.hpp:19
const std::string & back() const
return last reference token
Definition: json_pointer.hpp:270
std::string to_string() const
return a string representation of the JSON pointer
Definition: json_pointer.hpp:64
friend bool operator==(json_pointer const &lhs, json_pointer const &rhs) noexcept
compares two JSON pointers for equality
Definition: json_pointer.hpp:949
void pop_back()
remove last reference token
Definition: json_pointer.hpp:246
bool empty() const noexcept
return whether pointer points to the root document
Definition: json_pointer.hpp:317
friend bool operator!=(json_pointer const &lhs, json_pointer const &rhs) noexcept
compares two JSON pointers for inequality
Definition: json_pointer.hpp:966
void push_back(const std::string &token)
append an unescaped token at the end of the reference pointer
Definition: json_pointer.hpp:292
json_pointer & operator/=(const json_pointer &ptr)
append another JSON pointer at the end of this JSON pointer
Definition: json_pointer.hpp:96
json_pointer & operator/=(std::size_t array_idx)
append an array index at the end of this JSON pointer
Definition: json_pointer.hpp:142
json_pointer(const std::string &s="")
create JSON pointer
Definition: json_pointer.hpp:46
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:162
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:183
json_pointer & operator/=(std::string token)
append an unescaped reference token at the end of this JSON pointer
Definition: json_pointer.hpp:120
void push_back(std::string &&token)
append an unescaped token at the end of the reference pointer
Definition: json_pointer.hpp:298
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:203
json_pointer parent_pointer() const
returns the parent of this JSON pointer
Definition: json_pointer.hpp:221
@ object
object (unordered set of name/value pairs)
@ array
array (ordered collection of values)
@ value
the parser finished reading a JSON value
namespace for Niels Lohmann
Definition: adl_serializer.hpp:9