json_polymorphic_binder.hpp 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. //
  2. // json_polymorphic_binder.hpp
  3. // json
  4. //
  5. // Created by Sam Jaffe on 11/15/18.
  6. // Copyright © 2018 Sam Jaffe. All rights reserved.
  7. //
  8. #pragma once
  9. #include "json_binder.hpp"
  10. #include <functional>
  11. #include <map>
  12. #include <string>
  13. namespace json { namespace binder {
  14. template <typename PtrBase, typename Derived>
  15. class polymorphic_pointer_binder : public binder_impl<PtrBase> {
  16. public:
  17. polymorphic_pointer_binder(binder<Derived> impl) : m_impl(impl) {}
  18. virtual binder_impl<PtrBase> * clone() const {
  19. return new polymorphic_pointer_binder(*this);
  20. }
  21. virtual void parse(PtrBase & v, char const *& data,
  22. parser::options opts) const {
  23. if (!strncmp(data, "null", 4))
  24. v = nullptr;
  25. else {
  26. v = PtrBase(new Derived);
  27. Derived & actual = dynamic_cast<Derived &>(*v);
  28. m_impl.parse(actual, data, opts);
  29. }
  30. }
  31. void write(PtrBase const & v, std::ostream & os) const {
  32. if (v)
  33. m_impl.write(dynamic_cast<Derived const &>(*v), os);
  34. else
  35. os << "null";
  36. }
  37. private:
  38. binder<Derived> m_impl;
  39. };
  40. template <typename Ptr> class polymorphic_binder : public binder_impl<Ptr> {
  41. private:
  42. // Maybe a reference type
  43. using BaseR = decltype(*std::declval<Ptr>());
  44. using Base = typename std::remove_reference<BaseR>::type;
  45. static_assert(std::is_polymorphic<Base>::value,
  46. "Must use a polymorphic type");
  47. public:
  48. polymorphic_binder() = default;
  49. virtual binder_impl<Ptr> * clone() const override {
  50. return new polymorphic_binder(*this);
  51. }
  52. template <typename Derived>
  53. polymorphic_binder & operator()(std::string const & k,
  54. binder_impl<Derived> const & v) {
  55. mapping.emplace(k, polymorphic_pointer_binder<Ptr, Derived>(v));
  56. reverse_lookup.emplace(
  57. k, [](Base const * p) { return dynamic_cast<Derived const *>(p); });
  58. return *this;
  59. }
  60. virtual void parse(Ptr & object, char const *& data,
  61. parser::options opts) const override {
  62. const char ch = json::helper::get_next_element(data);
  63. if (ch == '{') {
  64. parse_object(object, ++data, opts);
  65. } else {
  66. throw not_an_object_exception(typeid(Base).name());
  67. }
  68. }
  69. virtual void write(Ptr const & val, std::ostream & data) const override {
  70. data << '{';
  71. using pair_t = typename decltype(reverse_lookup)::value_type;
  72. Base const * p = std::addressof(*val);
  73. auto it =
  74. std::find_if(reverse_lookup.begin(), reverse_lookup.end(),
  75. [p](pair_t const & pair) { return pair.second(p); });
  76. if (it == reverse_lookup.end()) {
  77. throw std::domain_error("Unknown JSON binding object");
  78. }
  79. data << "\"@id\":\"" << it->first << "\",";
  80. data << "\"@value\":";
  81. mapping.find(it->first)->second.write(val, data);
  82. data << '}';
  83. }
  84. void parse_object(Ptr & object, char const *& data,
  85. parser::options opts) const {
  86. std::string key;
  87. fetch_expected_key("@id", data);
  88. if (json::helper::get_next_element(++data) != '"') {
  89. throw json::malformed_json_exception{
  90. std::string("Expected polymorphic id starting with '\"', got '") +
  91. *data + "' instead"};
  92. }
  93. json::helper::parse_string(key, data);
  94. auto it = mapping.find(key);
  95. if (it == mapping.end()) {
  96. throw json::malformed_json_exception{"Unknown polymorphic type-id: '" +
  97. key + "'"};
  98. }
  99. fetch_expected_key("@value", ++data);
  100. it->second.parse(object, ++data, opts);
  101. if (!*data)
  102. throw unterminated_json_object();
  103. else if (*data != '}')
  104. throw json::malformed_json_exception{
  105. std::string("Unexpected non-terminated object '") + *data + "'"};
  106. else
  107. ++data;
  108. }
  109. void fetch_expected_key(std::string const & expected,
  110. char const *& data) const {
  111. std::string key;
  112. if (json::helper::get_next_element(data) != '"') {
  113. throw malformed_object_key(*data);
  114. }
  115. json::helper::parse_string(key, data);
  116. if (key != expected) {
  117. throw json::malformed_json_exception{"Expecting '" + expected +
  118. "' polymorphic token, got '" +
  119. key + "' instead"};
  120. }
  121. if (json::helper::get_next_element(data) != ':') {
  122. throw malformed_object_association(*data);
  123. }
  124. }
  125. private:
  126. std::map<std::string, std::function<bool(Base const *)>> reverse_lookup;
  127. std::map<std::string, binder<Ptr>> mapping;
  128. };
  129. }}