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