1 #pragma once 2 3 #include <algorithm> // all_of 4 #include <cassert> // assert 5 #include <cctype> // isdigit 6 #include <numeric> // accumulate 7 #include <string> // string 8 #include <utility> // move 9 #include <vector> // vector 10 11 #include <nlohmann/detail/exceptions.hpp> 12 #include <nlohmann/detail/macro_scope.hpp> 13 #include <nlohmann/detail/value_t.hpp> 14 15 namespace nlohmann 16 { 17 template<typename BasicJsonType> 18 class json_pointer 19 { 20 // allow basic_json to access private members 21 NLOHMANN_BASIC_JSON_TPL_DECLARATION 22 friend class basic_json; 23 24 public: 25 /*! 26 @brief create JSON pointer 27 28 Create a JSON pointer according to the syntax described in 29 [Section 3 of RFC6901](https://tools.ietf.org/html/rfc6901#section-3). 30 31 @param[in] s string representing the JSON pointer; if omitted, the empty 32 string is assumed which references the whole JSON value 33 34 @throw parse_error.107 if the given JSON pointer @a s is nonempty and does 35 not begin with a slash (`/`); see example below 36 37 @throw parse_error.108 if a tilde (`~`) in the given JSON pointer @a s is 38 not followed by `0` (representing `~`) or `1` (representing `/`); see 39 example below 40 41 @liveexample{The example shows the construction several valid JSON pointers 42 as well as the exceptional behavior.,json_pointer} 43 44 @since version 2.0.0 45 */ json_pointer(const std::string & s="")46 explicit json_pointer(const std::string& s = "") 47 : reference_tokens(split(s)) 48 {} 49 50 /*! 51 @brief return a string representation of the JSON pointer 52 53 @invariant For each JSON pointer `ptr`, it holds: 54 @code {.cpp} 55 ptr == json_pointer(ptr.to_string()); 56 @endcode 57 58 @return a string representation of the JSON pointer 59 60 @liveexample{The example shows the result of `to_string`.,json_pointer__to_string} 61 62 @since version 2.0.0 63 */ to_string() const64 std::string to_string() const 65 { 66 return std::accumulate(reference_tokens.begin(), reference_tokens.end(), 67 std::string{}, 68 [](const std::string & a, const std::string & b) 69 { 70 return a + "/" + escape(b); 71 }); 72 } 73 74 /// @copydoc to_string() operator std::string() const75 operator std::string() const 76 { 77 return to_string(); 78 } 79 80 /*! 81 @brief append another JSON pointer at the end of this JSON pointer 82 83 @param[in] ptr JSON pointer to append 84 @return JSON pointer with @a ptr appended 85 86 @liveexample{The example shows the usage of `operator/=`.,json_pointer__operator_add} 87 88 @complexity Linear in the length of @a ptr. 89 90 @sa @ref operator/=(std::string) to append a reference token 91 @sa @ref operator/=(std::size_t) to append an array index 92 @sa @ref operator/(const json_pointer&, const json_pointer&) for a binary operator 93 94 @since version 3.6.0 95 */ operator /=(const json_pointer & ptr)96 json_pointer& operator/=(const json_pointer& ptr) 97 { 98 reference_tokens.insert(reference_tokens.end(), 99 ptr.reference_tokens.begin(), 100 ptr.reference_tokens.end()); 101 return *this; 102 } 103 104 /*! 105 @brief append an unescaped reference token at the end of this JSON pointer 106 107 @param[in] token reference token to append 108 @return JSON pointer with @a token appended without escaping @a token 109 110 @liveexample{The example shows the usage of `operator/=`.,json_pointer__operator_add} 111 112 @complexity Amortized constant. 113 114 @sa @ref operator/=(const json_pointer&) to append a JSON pointer 115 @sa @ref operator/=(std::size_t) to append an array index 116 @sa @ref operator/(const json_pointer&, std::size_t) for a binary operator 117 118 @since version 3.6.0 119 */ operator /=(std::string token)120 json_pointer& operator/=(std::string token) 121 { 122 push_back(std::move(token)); 123 return *this; 124 } 125 126 /*! 127 @brief append an array index at the end of this JSON pointer 128 129 @param[in] array_index array index to append 130 @return JSON pointer with @a array_index appended 131 132 @liveexample{The example shows the usage of `operator/=`.,json_pointer__operator_add} 133 134 @complexity Amortized constant. 135 136 @sa @ref operator/=(const json_pointer&) to append a JSON pointer 137 @sa @ref operator/=(std::string) to append a reference token 138 @sa @ref operator/(const json_pointer&, std::string) for a binary operator 139 140 @since version 3.6.0 141 */ operator /=(std::size_t array_index)142 json_pointer& operator/=(std::size_t array_index) 143 { 144 return *this /= std::to_string(array_index); 145 } 146 147 /*! 148 @brief create a new JSON pointer by appending the right JSON pointer at the end of the left JSON pointer 149 150 @param[in] lhs JSON pointer 151 @param[in] rhs JSON pointer 152 @return a new JSON pointer with @a rhs appended to @a lhs 153 154 @liveexample{The example shows the usage of `operator/`.,json_pointer__operator_add_binary} 155 156 @complexity Linear in the length of @a lhs and @a rhs. 157 158 @sa @ref operator/=(const json_pointer&) to append a JSON pointer 159 160 @since version 3.6.0 161 */ operator /(const json_pointer & lhs,const json_pointer & rhs)162 friend json_pointer operator/(const json_pointer& lhs, 163 const json_pointer& rhs) 164 { 165 return json_pointer(lhs) /= rhs; 166 } 167 168 /*! 169 @brief create a new JSON pointer by appending the unescaped token at the end of the JSON pointer 170 171 @param[in] ptr JSON pointer 172 @param[in] token reference token 173 @return a new JSON pointer with unescaped @a token appended to @a ptr 174 175 @liveexample{The example shows the usage of `operator/`.,json_pointer__operator_add_binary} 176 177 @complexity Linear in the length of @a ptr. 178 179 @sa @ref operator/=(std::string) to append a reference token 180 181 @since version 3.6.0 182 */ operator /(const json_pointer & ptr,std::string token)183 friend json_pointer operator/(const json_pointer& ptr, std::string token) 184 { 185 return json_pointer(ptr) /= std::move(token); 186 } 187 188 /*! 189 @brief create a new JSON pointer by appending the array-index-token at the end of the JSON pointer 190 191 @param[in] ptr JSON pointer 192 @param[in] array_index array index 193 @return a new JSON pointer with @a array_index appended to @a ptr 194 195 @liveexample{The example shows the usage of `operator/`.,json_pointer__operator_add_binary} 196 197 @complexity Linear in the length of @a ptr. 198 199 @sa @ref operator/=(std::size_t) to append an array index 200 201 @since version 3.6.0 202 */ operator /(const json_pointer & ptr,std::size_t array_index)203 friend json_pointer operator/(const json_pointer& ptr, std::size_t array_index) 204 { 205 return json_pointer(ptr) /= array_index; 206 } 207 208 /*! 209 @brief returns the parent of this JSON pointer 210 211 @return parent of this JSON pointer; in case this JSON pointer is the root, 212 the root itself is returned 213 214 @complexity Linear in the length of the JSON pointer. 215 216 @liveexample{The example shows the result of `parent_pointer` for different 217 JSON Pointers.,json_pointer__parent_pointer} 218 219 @since version 3.6.0 220 */ parent_pointer() const221 json_pointer parent_pointer() const 222 { 223 if (empty()) 224 { 225 return *this; 226 } 227 228 json_pointer res = *this; 229 res.pop_back(); 230 return res; 231 } 232 233 /*! 234 @brief remove last reference token 235 236 @pre not `empty()` 237 238 @liveexample{The example shows the usage of `pop_back`.,json_pointer__pop_back} 239 240 @complexity Constant. 241 242 @throw out_of_range.405 if JSON pointer has no parent 243 244 @since version 3.6.0 245 */ pop_back()246 void pop_back() 247 { 248 if (JSON_HEDLEY_UNLIKELY(empty())) 249 { 250 JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); 251 } 252 253 reference_tokens.pop_back(); 254 } 255 256 /*! 257 @brief return last reference token 258 259 @pre not `empty()` 260 @return last reference token 261 262 @liveexample{The example shows the usage of `back`.,json_pointer__back} 263 264 @complexity Constant. 265 266 @throw out_of_range.405 if JSON pointer has no parent 267 268 @since version 3.6.0 269 */ back() const270 const std::string& back() const 271 { 272 if (JSON_HEDLEY_UNLIKELY(empty())) 273 { 274 JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); 275 } 276 277 return reference_tokens.back(); 278 } 279 280 /*! 281 @brief append an unescaped token at the end of the reference pointer 282 283 @param[in] token token to add 284 285 @complexity Amortized constant. 286 287 @liveexample{The example shows the result of `push_back` for different 288 JSON Pointers.,json_pointer__push_back} 289 290 @since version 3.6.0 291 */ push_back(const std::string & token)292 void push_back(const std::string& token) 293 { 294 reference_tokens.push_back(token); 295 } 296 297 /// @copydoc push_back(const std::string&) push_back(std::string && token)298 void push_back(std::string&& token) 299 { 300 reference_tokens.push_back(std::move(token)); 301 } 302 303 /*! 304 @brief return whether pointer points to the root document 305 306 @return true iff the JSON pointer points to the root document 307 308 @complexity Constant. 309 310 @exceptionsafety No-throw guarantee: this function never throws exceptions. 311 312 @liveexample{The example shows the result of `empty` for different JSON 313 Pointers.,json_pointer__empty} 314 315 @since version 3.6.0 316 */ empty() const317 bool empty() const noexcept 318 { 319 return reference_tokens.empty(); 320 } 321 322 private: 323 /*! 324 @param[in] s reference token to be converted into an array index 325 326 @return integer representation of @a s 327 328 @throw out_of_range.404 if string @a s could not be converted to an integer 329 */ array_index(const std::string & s)330 static int array_index(const std::string& s) 331 { 332 std::size_t processed_chars = 0; 333 const int res = std::stoi(s, &processed_chars); 334 335 // check if the string was completely read 336 if (JSON_HEDLEY_UNLIKELY(processed_chars != s.size())) 337 { 338 JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + s + "'")); 339 } 340 341 return res; 342 } 343 top() const344 json_pointer top() const 345 { 346 if (JSON_HEDLEY_UNLIKELY(empty())) 347 { 348 JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); 349 } 350 351 json_pointer result = *this; 352 result.reference_tokens = {reference_tokens[0]}; 353 return result; 354 } 355 356 /*! 357 @brief create and return a reference to the pointed to value 358 359 @complexity Linear in the number of reference tokens. 360 361 @throw parse_error.109 if array index is not a number 362 @throw type_error.313 if value cannot be unflattened 363 */ get_and_create(BasicJsonType & j) const364 BasicJsonType& get_and_create(BasicJsonType& j) const 365 { 366 using size_type = typename BasicJsonType::size_type; 367 auto result = &j; 368 369 // in case no reference tokens exist, return a reference to the JSON value 370 // j which will be overwritten by a primitive value 371 for (const auto& reference_token : reference_tokens) 372 { 373 switch (result->type()) 374 { 375 case detail::value_t::null: 376 { 377 if (reference_token == "0") 378 { 379 // start a new array if reference token is 0 380 result = &result->operator[](0); 381 } 382 else 383 { 384 // start a new object otherwise 385 result = &result->operator[](reference_token); 386 } 387 break; 388 } 389 390 case detail::value_t::object: 391 { 392 // create an entry in the object 393 result = &result->operator[](reference_token); 394 break; 395 } 396 397 case detail::value_t::array: 398 { 399 // create an entry in the array 400 JSON_TRY 401 { 402 result = &result->operator[](static_cast<size_type>(array_index(reference_token))); 403 } 404 JSON_CATCH(std::invalid_argument&) 405 { 406 JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); 407 } 408 break; 409 } 410 411 /* 412 The following code is only reached if there exists a reference 413 token _and_ the current value is primitive. In this case, we have 414 an error situation, because primitive values may only occur as 415 single value; that is, with an empty list of reference tokens. 416 */ 417 default: 418 JSON_THROW(detail::type_error::create(313, "invalid value to unflatten")); 419 } 420 } 421 422 return *result; 423 } 424 425 /*! 426 @brief return a reference to the pointed to value 427 428 @note This version does not throw if a value is not present, but tries to 429 create nested values instead. For instance, calling this function 430 with pointer `"/this/that"` on a null value is equivalent to calling 431 `operator[]("this").operator[]("that")` on that value, effectively 432 changing the null value to an object. 433 434 @param[in] ptr a JSON value 435 436 @return reference to the JSON value pointed to by the JSON pointer 437 438 @complexity Linear in the length of the JSON pointer. 439 440 @throw parse_error.106 if an array index begins with '0' 441 @throw parse_error.109 if an array index was not a number 442 @throw out_of_range.404 if the JSON pointer can not be resolved 443 */ get_unchecked(BasicJsonType * ptr) const444 BasicJsonType& get_unchecked(BasicJsonType* ptr) const 445 { 446 using size_type = typename BasicJsonType::size_type; 447 for (const auto& reference_token : reference_tokens) 448 { 449 // convert null values to arrays or objects before continuing 450 if (ptr->is_null()) 451 { 452 // check if reference token is a number 453 const bool nums = 454 std::all_of(reference_token.begin(), reference_token.end(), 455 [](const unsigned char x) 456 { 457 return std::isdigit(x); 458 }); 459 460 // change value to array for numbers or "-" or to object otherwise 461 *ptr = (nums or reference_token == "-") 462 ? detail::value_t::array 463 : detail::value_t::object; 464 } 465 466 switch (ptr->type()) 467 { 468 case detail::value_t::object: 469 { 470 // use unchecked object access 471 ptr = &ptr->operator[](reference_token); 472 break; 473 } 474 475 case detail::value_t::array: 476 { 477 // error condition (cf. RFC 6901, Sect. 4) 478 if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) 479 { 480 JSON_THROW(detail::parse_error::create(106, 0, 481 "array index '" + reference_token + 482 "' must not begin with '0'")); 483 } 484 485 if (reference_token == "-") 486 { 487 // explicitly treat "-" as index beyond the end 488 ptr = &ptr->operator[](ptr->m_value.array->size()); 489 } 490 else 491 { 492 // convert array index to number; unchecked access 493 JSON_TRY 494 { 495 ptr = &ptr->operator[]( 496 static_cast<size_type>(array_index(reference_token))); 497 } 498 JSON_CATCH(std::invalid_argument&) 499 { 500 JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); 501 } 502 } 503 break; 504 } 505 506 default: 507 JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); 508 } 509 } 510 511 return *ptr; 512 } 513 514 /*! 515 @throw parse_error.106 if an array index begins with '0' 516 @throw parse_error.109 if an array index was not a number 517 @throw out_of_range.402 if the array index '-' is used 518 @throw out_of_range.404 if the JSON pointer can not be resolved 519 */ get_checked(BasicJsonType * ptr) const520 BasicJsonType& get_checked(BasicJsonType* ptr) const 521 { 522 using size_type = typename BasicJsonType::size_type; 523 for (const auto& reference_token : reference_tokens) 524 { 525 switch (ptr->type()) 526 { 527 case detail::value_t::object: 528 { 529 // note: at performs range check 530 ptr = &ptr->at(reference_token); 531 break; 532 } 533 534 case detail::value_t::array: 535 { 536 if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) 537 { 538 // "-" always fails the range check 539 JSON_THROW(detail::out_of_range::create(402, 540 "array index '-' (" + std::to_string(ptr->m_value.array->size()) + 541 ") is out of range")); 542 } 543 544 // error condition (cf. RFC 6901, Sect. 4) 545 if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) 546 { 547 JSON_THROW(detail::parse_error::create(106, 0, 548 "array index '" + reference_token + 549 "' must not begin with '0'")); 550 } 551 552 // note: at performs range check 553 JSON_TRY 554 { 555 ptr = &ptr->at(static_cast<size_type>(array_index(reference_token))); 556 } 557 JSON_CATCH(std::invalid_argument&) 558 { 559 JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); 560 } 561 break; 562 } 563 564 default: 565 JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); 566 } 567 } 568 569 return *ptr; 570 } 571 572 /*! 573 @brief return a const reference to the pointed to value 574 575 @param[in] ptr a JSON value 576 577 @return const reference to the JSON value pointed to by the JSON 578 pointer 579 580 @throw parse_error.106 if an array index begins with '0' 581 @throw parse_error.109 if an array index was not a number 582 @throw out_of_range.402 if the array index '-' is used 583 @throw out_of_range.404 if the JSON pointer can not be resolved 584 */ get_unchecked(const BasicJsonType * ptr) const585 const BasicJsonType& get_unchecked(const BasicJsonType* ptr) const 586 { 587 using size_type = typename BasicJsonType::size_type; 588 for (const auto& reference_token : reference_tokens) 589 { 590 switch (ptr->type()) 591 { 592 case detail::value_t::object: 593 { 594 // use unchecked object access 595 ptr = &ptr->operator[](reference_token); 596 break; 597 } 598 599 case detail::value_t::array: 600 { 601 if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) 602 { 603 // "-" cannot be used for const access 604 JSON_THROW(detail::out_of_range::create(402, 605 "array index '-' (" + std::to_string(ptr->m_value.array->size()) + 606 ") is out of range")); 607 } 608 609 // error condition (cf. RFC 6901, Sect. 4) 610 if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) 611 { 612 JSON_THROW(detail::parse_error::create(106, 0, 613 "array index '" + reference_token + 614 "' must not begin with '0'")); 615 } 616 617 // use unchecked array access 618 JSON_TRY 619 { 620 ptr = &ptr->operator[]( 621 static_cast<size_type>(array_index(reference_token))); 622 } 623 JSON_CATCH(std::invalid_argument&) 624 { 625 JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); 626 } 627 break; 628 } 629 630 default: 631 JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); 632 } 633 } 634 635 return *ptr; 636 } 637 638 /*! 639 @throw parse_error.106 if an array index begins with '0' 640 @throw parse_error.109 if an array index was not a number 641 @throw out_of_range.402 if the array index '-' is used 642 @throw out_of_range.404 if the JSON pointer can not be resolved 643 */ get_checked(const BasicJsonType * ptr) const644 const BasicJsonType& get_checked(const BasicJsonType* ptr) const 645 { 646 using size_type = typename BasicJsonType::size_type; 647 for (const auto& reference_token : reference_tokens) 648 { 649 switch (ptr->type()) 650 { 651 case detail::value_t::object: 652 { 653 // note: at performs range check 654 ptr = &ptr->at(reference_token); 655 break; 656 } 657 658 case detail::value_t::array: 659 { 660 if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) 661 { 662 // "-" always fails the range check 663 JSON_THROW(detail::out_of_range::create(402, 664 "array index '-' (" + std::to_string(ptr->m_value.array->size()) + 665 ") is out of range")); 666 } 667 668 // error condition (cf. RFC 6901, Sect. 4) 669 if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) 670 { 671 JSON_THROW(detail::parse_error::create(106, 0, 672 "array index '" + reference_token + 673 "' must not begin with '0'")); 674 } 675 676 // note: at performs range check 677 JSON_TRY 678 { 679 ptr = &ptr->at(static_cast<size_type>(array_index(reference_token))); 680 } 681 JSON_CATCH(std::invalid_argument&) 682 { 683 JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); 684 } 685 break; 686 } 687 688 default: 689 JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); 690 } 691 } 692 693 return *ptr; 694 } 695 696 /*! 697 @throw parse_error.106 if an array index begins with '0' 698 @throw parse_error.109 if an array index was not a number 699 */ contains(const BasicJsonType * ptr) const700 bool contains(const BasicJsonType* ptr) const 701 { 702 using size_type = typename BasicJsonType::size_type; 703 for (const auto& reference_token : reference_tokens) 704 { 705 switch (ptr->type()) 706 { 707 case detail::value_t::object: 708 { 709 if (not ptr->contains(reference_token)) 710 { 711 // we did not find the key in the object 712 return false; 713 } 714 715 ptr = &ptr->operator[](reference_token); 716 break; 717 } 718 719 case detail::value_t::array: 720 { 721 if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) 722 { 723 // "-" always fails the range check 724 return false; 725 } 726 727 // error condition (cf. RFC 6901, Sect. 4) 728 if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) 729 { 730 JSON_THROW(detail::parse_error::create(106, 0, 731 "array index '" + reference_token + 732 "' must not begin with '0'")); 733 } 734 735 JSON_TRY 736 { 737 const auto idx = static_cast<size_type>(array_index(reference_token)); 738 if (idx >= ptr->size()) 739 { 740 // index out of range 741 return false; 742 } 743 744 ptr = &ptr->operator[](idx); 745 break; 746 } 747 JSON_CATCH(std::invalid_argument&) 748 { 749 JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); 750 } 751 break; 752 } 753 754 default: 755 { 756 // we do not expect primitive values if there is still a 757 // reference token to process 758 return false; 759 } 760 } 761 } 762 763 // no reference token left means we found a primitive value 764 return true; 765 } 766 767 /*! 768 @brief split the string input to reference tokens 769 770 @note This function is only called by the json_pointer constructor. 771 All exceptions below are documented there. 772 773 @throw parse_error.107 if the pointer is not empty or begins with '/' 774 @throw parse_error.108 if character '~' is not followed by '0' or '1' 775 */ split(const std::string & reference_string)776 static std::vector<std::string> split(const std::string& reference_string) 777 { 778 std::vector<std::string> result; 779 780 // special case: empty reference string -> no reference tokens 781 if (reference_string.empty()) 782 { 783 return result; 784 } 785 786 // check if nonempty reference string begins with slash 787 if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/')) 788 { 789 JSON_THROW(detail::parse_error::create(107, 1, 790 "JSON pointer must be empty or begin with '/' - was: '" + 791 reference_string + "'")); 792 } 793 794 // extract the reference tokens: 795 // - slash: position of the last read slash (or end of string) 796 // - start: position after the previous slash 797 for ( 798 // search for the first slash after the first character 799 std::size_t slash = reference_string.find_first_of('/', 1), 800 // set the beginning of the first reference token 801 start = 1; 802 // we can stop if start == 0 (if slash == std::string::npos) 803 start != 0; 804 // set the beginning of the next reference token 805 // (will eventually be 0 if slash == std::string::npos) 806 start = (slash == std::string::npos) ? 0 : slash + 1, 807 // find next slash 808 slash = reference_string.find_first_of('/', start)) 809 { 810 // use the text between the beginning of the reference token 811 // (start) and the last slash (slash). 812 auto reference_token = reference_string.substr(start, slash - start); 813 814 // check reference tokens are properly escaped 815 for (std::size_t pos = reference_token.find_first_of('~'); 816 pos != std::string::npos; 817 pos = reference_token.find_first_of('~', pos + 1)) 818 { 819 assert(reference_token[pos] == '~'); 820 821 // ~ must be followed by 0 or 1 822 if (JSON_HEDLEY_UNLIKELY(pos == reference_token.size() - 1 or 823 (reference_token[pos + 1] != '0' and 824 reference_token[pos + 1] != '1'))) 825 { 826 JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'")); 827 } 828 } 829 830 // finally, store the reference token 831 unescape(reference_token); 832 result.push_back(reference_token); 833 } 834 835 return result; 836 } 837 838 /*! 839 @brief replace all occurrences of a substring by another string 840 841 @param[in,out] s the string to manipulate; changed so that all 842 occurrences of @a f are replaced with @a t 843 @param[in] f the substring to replace with @a t 844 @param[in] t the string to replace @a f 845 846 @pre The search string @a f must not be empty. **This precondition is 847 enforced with an assertion.** 848 849 @since version 2.0.0 850 */ replace_substring(std::string & s,const std::string & f,const std::string & t)851 static void replace_substring(std::string& s, const std::string& f, 852 const std::string& t) 853 { 854 assert(not f.empty()); 855 for (auto pos = s.find(f); // find first occurrence of f 856 pos != std::string::npos; // make sure f was found 857 s.replace(pos, f.size(), t), // replace with t, and 858 pos = s.find(f, pos + t.size())) // find next occurrence of f 859 {} 860 } 861 862 /// escape "~" to "~0" and "/" to "~1" escape(std::string s)863 static std::string escape(std::string s) 864 { 865 replace_substring(s, "~", "~0"); 866 replace_substring(s, "/", "~1"); 867 return s; 868 } 869 870 /// unescape "~1" to tilde and "~0" to slash (order is important!) unescape(std::string & s)871 static void unescape(std::string& s) 872 { 873 replace_substring(s, "~1", "/"); 874 replace_substring(s, "~0", "~"); 875 } 876 877 /*! 878 @param[in] reference_string the reference string to the current value 879 @param[in] value the value to consider 880 @param[in,out] result the result object to insert values to 881 882 @note Empty objects or arrays are flattened to `null`. 883 */ flatten(const std::string & reference_string,const BasicJsonType & value,BasicJsonType & result)884 static void flatten(const std::string& reference_string, 885 const BasicJsonType& value, 886 BasicJsonType& result) 887 { 888 switch (value.type()) 889 { 890 case detail::value_t::array: 891 { 892 if (value.m_value.array->empty()) 893 { 894 // flatten empty array as null 895 result[reference_string] = nullptr; 896 } 897 else 898 { 899 // iterate array and use index as reference string 900 for (std::size_t i = 0; i < value.m_value.array->size(); ++i) 901 { 902 flatten(reference_string + "/" + std::to_string(i), 903 value.m_value.array->operator[](i), result); 904 } 905 } 906 break; 907 } 908 909 case detail::value_t::object: 910 { 911 if (value.m_value.object->empty()) 912 { 913 // flatten empty object as null 914 result[reference_string] = nullptr; 915 } 916 else 917 { 918 // iterate object and use keys as reference string 919 for (const auto& element : *value.m_value.object) 920 { 921 flatten(reference_string + "/" + escape(element.first), element.second, result); 922 } 923 } 924 break; 925 } 926 927 default: 928 { 929 // add primitive value with its reference string 930 result[reference_string] = value; 931 break; 932 } 933 } 934 } 935 936 /*! 937 @param[in] value flattened JSON 938 939 @return unflattened JSON 940 941 @throw parse_error.109 if array index is not a number 942 @throw type_error.314 if value is not an object 943 @throw type_error.315 if object values are not primitive 944 @throw type_error.313 if value cannot be unflattened 945 */ 946 static BasicJsonType unflatten(const BasicJsonType & value)947 unflatten(const BasicJsonType& value) 948 { 949 if (JSON_HEDLEY_UNLIKELY(not value.is_object())) 950 { 951 JSON_THROW(detail::type_error::create(314, "only objects can be unflattened")); 952 } 953 954 BasicJsonType result; 955 956 // iterate the JSON object values 957 for (const auto& element : *value.m_value.object) 958 { 959 if (JSON_HEDLEY_UNLIKELY(not element.second.is_primitive())) 960 { 961 JSON_THROW(detail::type_error::create(315, "values in object must be primitive")); 962 } 963 964 // assign value to reference pointed to by JSON pointer; Note that if 965 // the JSON pointer is "" (i.e., points to the whole value), function 966 // get_and_create returns a reference to result itself. An assignment 967 // will then create a primitive value. 968 json_pointer(element.first).get_and_create(result) = element.second; 969 } 970 971 return result; 972 } 973 974 /*! 975 @brief compares two JSON pointers for equality 976 977 @param[in] lhs JSON pointer to compare 978 @param[in] rhs JSON pointer to compare 979 @return whether @a lhs is equal to @a rhs 980 981 @complexity Linear in the length of the JSON pointer 982 983 @exceptionsafety No-throw guarantee: this function never throws exceptions. 984 */ operator ==(json_pointer const & lhs,json_pointer const & rhs)985 friend bool operator==(json_pointer const& lhs, 986 json_pointer const& rhs) noexcept 987 { 988 return lhs.reference_tokens == rhs.reference_tokens; 989 } 990 991 /*! 992 @brief compares two JSON pointers for inequality 993 994 @param[in] lhs JSON pointer to compare 995 @param[in] rhs JSON pointer to compare 996 @return whether @a lhs is not equal @a rhs 997 998 @complexity Linear in the length of the JSON pointer 999 1000 @exceptionsafety No-throw guarantee: this function never throws exceptions. 1001 */ operator !=(json_pointer const & lhs,json_pointer const & rhs)1002 friend bool operator!=(json_pointer const& lhs, 1003 json_pointer const& rhs) noexcept 1004 { 1005 return not (lhs == rhs); 1006 } 1007 1008 /// the reference tokens 1009 std::vector<std::string> reference_tokens; 1010 }; 1011 } // namespace nlohmann 1012