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/string_escape.hpp>
14 #include <nlohmann/detail/value_t.hpp>
15 
16 namespace nlohmann
17 {
18 template<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 == "-")
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
const std::string & back() const
return last reference token
Definition: json_pointer.hpp:271
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
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 & operator/=(const json_pointer &ptr)
append another JSON pointer at the end of this JSON pointer
Definition: json_pointer.hpp:97
json_pointer & operator/=(std::size_t array_idx)
append an array index at the end of this JSON pointer
Definition: json_pointer.hpp:143
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
json_pointer & operator/=(std::string token)
append an unescaped reference token at the end of this JSON pointer
Definition: json_pointer.hpp:121
void push_back(std::string &&token)
append an unescaped token at the end of the reference pointer
Definition: json_pointer.hpp:299
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
@ 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)
@ value
the parser finished reading a JSON value
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