json_pointer.hpp 32 KB

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