json_pointer.hpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696
  1. #pragma once
  2. #include <cassert> // assert
  3. #include <numeric> // accumulate
  4. #include <string> // string
  5. #include <vector> // vector
  6. #include <nlohmann/detail/macro_scope.hpp>
  7. #include <nlohmann/detail/exceptions.hpp>
  8. #include <nlohmann/detail/value_t.hpp>
  9. namespace nlohmann
  10. {
  11. template<typename BasicJsonType>
  12. class json_pointer
  13. {
  14. // allow basic_json to access private members
  15. NLOHMANN_BASIC_JSON_TPL_DECLARATION
  16. friend class basic_json;
  17. public:
  18. /*!
  19. @brief create JSON pointer
  20. Create a JSON pointer according to the syntax described in
  21. [Section 3 of RFC6901](https://tools.ietf.org/html/rfc6901#section-3).
  22. @param[in] s string representing the JSON pointer; if omitted, the empty
  23. string is assumed which references the whole JSON value
  24. @throw parse_error.107 if the given JSON pointer @a s is nonempty and does
  25. not begin with a slash (`/`); see example below
  26. @throw parse_error.108 if a tilde (`~`) in the given JSON pointer @a s is
  27. not followed by `0` (representing `~`) or `1` (representing `/`); see
  28. example below
  29. @liveexample{The example shows the construction several valid JSON pointers
  30. as well as the exceptional behavior.,json_pointer}
  31. @since version 2.0.0
  32. */
  33. explicit json_pointer(const std::string& s = "")
  34. : reference_tokens(split(s))
  35. {}
  36. /*!
  37. @brief return a string representation of the JSON pointer
  38. @invariant For each JSON pointer `ptr`, it holds:
  39. @code {.cpp}
  40. ptr == json_pointer(ptr.to_string());
  41. @endcode
  42. @return a string representation of the JSON pointer
  43. @liveexample{The example shows the result of `to_string`.,
  44. json_pointer__to_string}
  45. @since version 2.0.0
  46. */
  47. std::string to_string() const noexcept
  48. {
  49. return std::accumulate(reference_tokens.begin(), reference_tokens.end(),
  50. std::string{},
  51. [](const std::string & a, const std::string & b)
  52. {
  53. return a + "/" + escape(b);
  54. });
  55. }
  56. /// @copydoc to_string()
  57. operator std::string() const
  58. {
  59. return to_string();
  60. }
  61. /*!
  62. @param[in] s reference token to be converted into an array index
  63. @return integer representation of @a s
  64. @throw out_of_range.404 if string @a s could not be converted to an integer
  65. */
  66. static int array_index(const std::string& s)
  67. {
  68. std::size_t processed_chars = 0;
  69. const int res = std::stoi(s, &processed_chars);
  70. // check if the string was completely read
  71. if (JSON_UNLIKELY(processed_chars != s.size()))
  72. {
  73. JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + s + "'"));
  74. }
  75. return res;
  76. }
  77. private:
  78. /*!
  79. @brief remove and return last reference pointer
  80. @throw out_of_range.405 if JSON pointer has no parent
  81. */
  82. std::string pop_back()
  83. {
  84. if (JSON_UNLIKELY(is_root()))
  85. {
  86. JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent"));
  87. }
  88. auto last = reference_tokens.back();
  89. reference_tokens.pop_back();
  90. return last;
  91. }
  92. /// return whether pointer points to the root document
  93. bool is_root() const
  94. {
  95. return reference_tokens.empty();
  96. }
  97. json_pointer top() const
  98. {
  99. if (JSON_UNLIKELY(is_root()))
  100. {
  101. JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent"));
  102. }
  103. json_pointer result = *this;
  104. result.reference_tokens = {reference_tokens[0]};
  105. return result;
  106. }
  107. /*!
  108. @brief create and return a reference to the pointed to value
  109. @complexity Linear in the number of reference tokens.
  110. @throw parse_error.109 if array index is not a number
  111. @throw type_error.313 if value cannot be unflattened
  112. */
  113. BasicJsonType& get_and_create(BasicJsonType& j) const
  114. {
  115. using size_type = typename BasicJsonType::size_type;
  116. auto result = &j;
  117. // in case no reference tokens exist, return a reference to the JSON value
  118. // j which will be overwritten by a primitive value
  119. for (const auto& reference_token : reference_tokens)
  120. {
  121. switch (result->m_type)
  122. {
  123. case detail::value_t::null:
  124. {
  125. if (reference_token == "0")
  126. {
  127. // start a new array if reference token is 0
  128. result = &result->operator[](0);
  129. }
  130. else
  131. {
  132. // start a new object otherwise
  133. result = &result->operator[](reference_token);
  134. }
  135. break;
  136. }
  137. case detail::value_t::object:
  138. {
  139. // create an entry in the object
  140. result = &result->operator[](reference_token);
  141. break;
  142. }
  143. case detail::value_t::array:
  144. {
  145. // create an entry in the array
  146. JSON_TRY
  147. {
  148. result = &result->operator[](static_cast<size_type>(array_index(reference_token)));
  149. }
  150. JSON_CATCH(std::invalid_argument&)
  151. {
  152. JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
  153. }
  154. break;
  155. }
  156. /*
  157. The following code is only reached if there exists a reference
  158. token _and_ the current value is primitive. In this case, we have
  159. an error situation, because primitive values may only occur as
  160. single value; that is, with an empty list of reference tokens.
  161. */
  162. default:
  163. JSON_THROW(detail::type_error::create(313, "invalid value to unflatten"));
  164. }
  165. }
  166. return *result;
  167. }
  168. /*!
  169. @brief return a reference to the pointed to value
  170. @note This version does not throw if a value is not present, but tries to
  171. create nested values instead. For instance, calling this function
  172. with pointer `"/this/that"` on a null value is equivalent to calling
  173. `operator[]("this").operator[]("that")` on that value, effectively
  174. changing the null value to an object.
  175. @param[in] ptr a JSON value
  176. @return reference to the JSON value pointed to by the JSON pointer
  177. @complexity Linear in the length of the JSON pointer.
  178. @throw parse_error.106 if an array index begins with '0'
  179. @throw parse_error.109 if an array index was not a number
  180. @throw out_of_range.404 if the JSON pointer can not be resolved
  181. */
  182. BasicJsonType& get_unchecked(BasicJsonType* ptr) const
  183. {
  184. using size_type = typename BasicJsonType::size_type;
  185. for (const auto& reference_token : reference_tokens)
  186. {
  187. // convert null values to arrays or objects before continuing
  188. if (ptr->m_type == detail::value_t::null)
  189. {
  190. // check if reference token is a number
  191. const bool nums =
  192. std::all_of(reference_token.begin(), reference_token.end(),
  193. [](const char x)
  194. {
  195. return (x >= '0' and x <= '9');
  196. });
  197. // change value to array for numbers or "-" or to object otherwise
  198. *ptr = (nums or reference_token == "-")
  199. ? detail::value_t::array
  200. : detail::value_t::object;
  201. }
  202. switch (ptr->m_type)
  203. {
  204. case detail::value_t::object:
  205. {
  206. // use unchecked object access
  207. ptr = &ptr->operator[](reference_token);
  208. break;
  209. }
  210. case detail::value_t::array:
  211. {
  212. // error condition (cf. RFC 6901, Sect. 4)
  213. if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0'))
  214. {
  215. JSON_THROW(detail::parse_error::create(106, 0,
  216. "array index '" + reference_token +
  217. "' must not begin with '0'"));
  218. }
  219. if (reference_token == "-")
  220. {
  221. // explicitly treat "-" as index beyond the end
  222. ptr = &ptr->operator[](ptr->m_value.array->size());
  223. }
  224. else
  225. {
  226. // convert array index to number; unchecked access
  227. JSON_TRY
  228. {
  229. ptr = &ptr->operator[](
  230. static_cast<size_type>(array_index(reference_token)));
  231. }
  232. JSON_CATCH(std::invalid_argument&)
  233. {
  234. JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
  235. }
  236. }
  237. break;
  238. }
  239. default:
  240. JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'"));
  241. }
  242. }
  243. return *ptr;
  244. }
  245. /*!
  246. @throw parse_error.106 if an array index begins with '0'
  247. @throw parse_error.109 if an array index was not a number
  248. @throw out_of_range.402 if the array index '-' is used
  249. @throw out_of_range.404 if the JSON pointer can not be resolved
  250. */
  251. BasicJsonType& get_checked(BasicJsonType* ptr) const
  252. {
  253. using size_type = typename BasicJsonType::size_type;
  254. for (const auto& reference_token : reference_tokens)
  255. {
  256. switch (ptr->m_type)
  257. {
  258. case detail::value_t::object:
  259. {
  260. // note: at performs range check
  261. ptr = &ptr->at(reference_token);
  262. break;
  263. }
  264. case detail::value_t::array:
  265. {
  266. if (JSON_UNLIKELY(reference_token == "-"))
  267. {
  268. // "-" always fails the range check
  269. JSON_THROW(detail::out_of_range::create(402,
  270. "array index '-' (" + std::to_string(ptr->m_value.array->size()) +
  271. ") is out of range"));
  272. }
  273. // error condition (cf. RFC 6901, Sect. 4)
  274. if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0'))
  275. {
  276. JSON_THROW(detail::parse_error::create(106, 0,
  277. "array index '" + reference_token +
  278. "' must not begin with '0'"));
  279. }
  280. // note: at performs range check
  281. JSON_TRY
  282. {
  283. ptr = &ptr->at(static_cast<size_type>(array_index(reference_token)));
  284. }
  285. JSON_CATCH(std::invalid_argument&)
  286. {
  287. JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
  288. }
  289. break;
  290. }
  291. default:
  292. JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'"));
  293. }
  294. }
  295. return *ptr;
  296. }
  297. /*!
  298. @brief return a const reference to the pointed to value
  299. @param[in] ptr a JSON value
  300. @return const reference to the JSON value pointed to by the JSON
  301. pointer
  302. @throw parse_error.106 if an array index begins with '0'
  303. @throw parse_error.109 if an array index was not a number
  304. @throw out_of_range.402 if the array index '-' is used
  305. @throw out_of_range.404 if the JSON pointer can not be resolved
  306. */
  307. const BasicJsonType& get_unchecked(const BasicJsonType* ptr) const
  308. {
  309. using size_type = typename BasicJsonType::size_type;
  310. for (const auto& reference_token : reference_tokens)
  311. {
  312. switch (ptr->m_type)
  313. {
  314. case detail::value_t::object:
  315. {
  316. // use unchecked object access
  317. ptr = &ptr->operator[](reference_token);
  318. break;
  319. }
  320. case detail::value_t::array:
  321. {
  322. if (JSON_UNLIKELY(reference_token == "-"))
  323. {
  324. // "-" cannot be used for const access
  325. JSON_THROW(detail::out_of_range::create(402,
  326. "array index '-' (" + std::to_string(ptr->m_value.array->size()) +
  327. ") is out of range"));
  328. }
  329. // error condition (cf. RFC 6901, Sect. 4)
  330. if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0'))
  331. {
  332. JSON_THROW(detail::parse_error::create(106, 0,
  333. "array index '" + reference_token +
  334. "' must not begin with '0'"));
  335. }
  336. // use unchecked array access
  337. JSON_TRY
  338. {
  339. ptr = &ptr->operator[](
  340. static_cast<size_type>(array_index(reference_token)));
  341. }
  342. JSON_CATCH(std::invalid_argument&)
  343. {
  344. JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
  345. }
  346. break;
  347. }
  348. default:
  349. JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'"));
  350. }
  351. }
  352. return *ptr;
  353. }
  354. /*!
  355. @throw parse_error.106 if an array index begins with '0'
  356. @throw parse_error.109 if an array index was not a number
  357. @throw out_of_range.402 if the array index '-' is used
  358. @throw out_of_range.404 if the JSON pointer can not be resolved
  359. */
  360. const BasicJsonType& get_checked(const BasicJsonType* ptr) const
  361. {
  362. using size_type = typename BasicJsonType::size_type;
  363. for (const auto& reference_token : reference_tokens)
  364. {
  365. switch (ptr->m_type)
  366. {
  367. case detail::value_t::object:
  368. {
  369. // note: at performs range check
  370. ptr = &ptr->at(reference_token);
  371. break;
  372. }
  373. case detail::value_t::array:
  374. {
  375. if (JSON_UNLIKELY(reference_token == "-"))
  376. {
  377. // "-" always fails the range check
  378. JSON_THROW(detail::out_of_range::create(402,
  379. "array index '-' (" + std::to_string(ptr->m_value.array->size()) +
  380. ") is out of range"));
  381. }
  382. // error condition (cf. RFC 6901, Sect. 4)
  383. if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0'))
  384. {
  385. JSON_THROW(detail::parse_error::create(106, 0,
  386. "array index '" + reference_token +
  387. "' must not begin with '0'"));
  388. }
  389. // note: at performs range check
  390. JSON_TRY
  391. {
  392. ptr = &ptr->at(static_cast<size_type>(array_index(reference_token)));
  393. }
  394. JSON_CATCH(std::invalid_argument&)
  395. {
  396. JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
  397. }
  398. break;
  399. }
  400. default:
  401. JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'"));
  402. }
  403. }
  404. return *ptr;
  405. }
  406. /*!
  407. @brief split the string input to reference tokens
  408. @note This function is only called by the json_pointer constructor.
  409. All exceptions below are documented there.
  410. @throw parse_error.107 if the pointer is not empty or begins with '/'
  411. @throw parse_error.108 if character '~' is not followed by '0' or '1'
  412. */
  413. static std::vector<std::string> split(const std::string& reference_string)
  414. {
  415. std::vector<std::string> result;
  416. // special case: empty reference string -> no reference tokens
  417. if (reference_string.empty())
  418. {
  419. return result;
  420. }
  421. // check if nonempty reference string begins with slash
  422. if (JSON_UNLIKELY(reference_string[0] != '/'))
  423. {
  424. JSON_THROW(detail::parse_error::create(107, 1,
  425. "JSON pointer must be empty or begin with '/' - was: '" +
  426. reference_string + "'"));
  427. }
  428. // extract the reference tokens:
  429. // - slash: position of the last read slash (or end of string)
  430. // - start: position after the previous slash
  431. for (
  432. // search for the first slash after the first character
  433. std::size_t slash = reference_string.find_first_of('/', 1),
  434. // set the beginning of the first reference token
  435. start = 1;
  436. // we can stop if start == string::npos+1 = 0
  437. start != 0;
  438. // set the beginning of the next reference token
  439. // (will eventually be 0 if slash == std::string::npos)
  440. start = slash + 1,
  441. // find next slash
  442. slash = reference_string.find_first_of('/', start))
  443. {
  444. // use the text between the beginning of the reference token
  445. // (start) and the last slash (slash).
  446. auto reference_token = reference_string.substr(start, slash - start);
  447. // check reference tokens are properly escaped
  448. for (std::size_t pos = reference_token.find_first_of('~');
  449. pos != std::string::npos;
  450. pos = reference_token.find_first_of('~', pos + 1))
  451. {
  452. assert(reference_token[pos] == '~');
  453. // ~ must be followed by 0 or 1
  454. if (JSON_UNLIKELY(pos == reference_token.size() - 1 or
  455. (reference_token[pos + 1] != '0' and
  456. reference_token[pos + 1] != '1')))
  457. {
  458. JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'"));
  459. }
  460. }
  461. // finally, store the reference token
  462. unescape(reference_token);
  463. result.push_back(reference_token);
  464. }
  465. return result;
  466. }
  467. /*!
  468. @brief replace all occurrences of a substring by another string
  469. @param[in,out] s the string to manipulate; changed so that all
  470. occurrences of @a f are replaced with @a t
  471. @param[in] f the substring to replace with @a t
  472. @param[in] t the string to replace @a f
  473. @pre The search string @a f must not be empty. **This precondition is
  474. enforced with an assertion.**
  475. @since version 2.0.0
  476. */
  477. static void replace_substring(std::string& s, const std::string& f,
  478. const std::string& t)
  479. {
  480. assert(not f.empty());
  481. for (auto pos = s.find(f); // find first occurrence of f
  482. pos != std::string::npos; // make sure f was found
  483. s.replace(pos, f.size(), t), // replace with t, and
  484. pos = s.find(f, pos + t.size())) // find next occurrence of f
  485. {}
  486. }
  487. /// escape "~"" to "~0" and "/" to "~1"
  488. static std::string escape(std::string s)
  489. {
  490. replace_substring(s, "~", "~0");
  491. replace_substring(s, "/", "~1");
  492. return s;
  493. }
  494. /// unescape "~1" to tilde and "~0" to slash (order is important!)
  495. static void unescape(std::string& s)
  496. {
  497. replace_substring(s, "~1", "/");
  498. replace_substring(s, "~0", "~");
  499. }
  500. /*!
  501. @param[in] reference_string the reference string to the current value
  502. @param[in] value the value to consider
  503. @param[in,out] result the result object to insert values to
  504. @note Empty objects or arrays are flattened to `null`.
  505. */
  506. static void flatten(const std::string& reference_string,
  507. const BasicJsonType& value,
  508. BasicJsonType& result)
  509. {
  510. switch (value.m_type)
  511. {
  512. case detail::value_t::array:
  513. {
  514. if (value.m_value.array->empty())
  515. {
  516. // flatten empty array as null
  517. result[reference_string] = nullptr;
  518. }
  519. else
  520. {
  521. // iterate array and use index as reference string
  522. for (std::size_t i = 0; i < value.m_value.array->size(); ++i)
  523. {
  524. flatten(reference_string + "/" + std::to_string(i),
  525. value.m_value.array->operator[](i), result);
  526. }
  527. }
  528. break;
  529. }
  530. case detail::value_t::object:
  531. {
  532. if (value.m_value.object->empty())
  533. {
  534. // flatten empty object as null
  535. result[reference_string] = nullptr;
  536. }
  537. else
  538. {
  539. // iterate object and use keys as reference string
  540. for (const auto& element : *value.m_value.object)
  541. {
  542. flatten(reference_string + "/" + escape(element.first), element.second, result);
  543. }
  544. }
  545. break;
  546. }
  547. default:
  548. {
  549. // add primitive value with its reference string
  550. result[reference_string] = value;
  551. break;
  552. }
  553. }
  554. }
  555. /*!
  556. @param[in] value flattened JSON
  557. @return unflattened JSON
  558. @throw parse_error.109 if array index is not a number
  559. @throw type_error.314 if value is not an object
  560. @throw type_error.315 if object values are not primitive
  561. @throw type_error.313 if value cannot be unflattened
  562. */
  563. static BasicJsonType
  564. unflatten(const BasicJsonType& value)
  565. {
  566. if (JSON_UNLIKELY(not value.is_object()))
  567. {
  568. JSON_THROW(detail::type_error::create(314, "only objects can be unflattened"));
  569. }
  570. BasicJsonType result;
  571. // iterate the JSON object values
  572. for (const auto& element : *value.m_value.object)
  573. {
  574. if (JSON_UNLIKELY(not element.second.is_primitive()))
  575. {
  576. JSON_THROW(detail::type_error::create(315, "values in object must be primitive"));
  577. }
  578. // assign value to reference pointed to by JSON pointer; Note that if
  579. // the JSON pointer is "" (i.e., points to the whole value), function
  580. // get_and_create returns a reference to result itself. An assignment
  581. // will then create a primitive value.
  582. json_pointer(element.first).get_and_create(result) = element.second;
  583. }
  584. return result;
  585. }
  586. friend bool operator==(json_pointer const& lhs,
  587. json_pointer const& rhs) noexcept
  588. {
  589. return (lhs.reference_tokens == rhs.reference_tokens);
  590. }
  591. friend bool operator!=(json_pointer const& lhs,
  592. json_pointer const& rhs) noexcept
  593. {
  594. return not (lhs == rhs);
  595. }
  596. /// the reference tokens
  597. std::vector<std::string> reference_tokens;
  598. };
  599. }