json_polymorphic_binder.hpp 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  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)) v = nullptr;
  24. else {
  25. v = PtrBase(new Derived);
  26. Derived & actual = dynamic_cast<Derived &>(*v);
  27. m_impl.parse(actual, data, opts);
  28. }
  29. }
  30. void write(PtrBase const & v, std::ostream & os) const {
  31. if (v) m_impl.write(dynamic_cast<Derived const &>(*v), os);
  32. else os << "null";
  33. }
  34. private:
  35. binder<Derived> m_impl;
  36. };
  37. template <typename Ptr>
  38. class polymorphic_binder : public binder_impl<Ptr> {
  39. private:
  40. // Maybe a reference type
  41. using BaseR = decltype(*std::declval<Ptr>());
  42. using Base = typename std::remove_reference<BaseR>::type;
  43. static_assert(std::is_polymorphic<Base>::value,
  44. "Must use a polymorphic type");
  45. public:
  46. polymorphic_binder() = default;
  47. virtual binder_impl<Ptr>* clone() const override {
  48. return new polymorphic_binder(*this);
  49. }
  50. template <typename Derived>
  51. polymorphic_binder& operator()(std::string const&k,
  52. binder_impl<Derived> const&v) {
  53. mapping.emplace(k, polymorphic_pointer_binder<Ptr, Derived>(v));
  54. reverse_lookup.emplace(k, [](Base const * p) {
  55. return dynamic_cast<Derived const *>(p);
  56. });
  57. return *this;
  58. }
  59. virtual void parse(Ptr & object, char const*& data,
  60. parser::options opts) const override {
  61. const char ch = json::helper::get_next_element(data);
  62. if (ch == '{') {
  63. parse_object(object, ++data, opts);
  64. } else {
  65. throw not_an_object_exception(typeid(Base).name());
  66. }
  67. }
  68. virtual void write(Ptr const & val, std::ostream & data) const override {
  69. data << '{';
  70. using pair_t = typename decltype(reverse_lookup)::value_type;
  71. Base const * p = std::addressof(*val);
  72. auto it = std::find_if(reverse_lookup.begin(), reverse_lookup.end(),
  73. [p](pair_t const & pair) {
  74. return pair.second(p);
  75. });
  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. }
  94. json::helper::parse_string(key, data);
  95. auto it = mapping.find(key);
  96. if (it == mapping.end()) {
  97. throw json::malformed_json_exception{
  98. "Unknown polymorphic type-id: '" + key + "'"
  99. };
  100. }
  101. fetch_expected_key("@value", ++data);
  102. it->second.parse(object, ++data, opts);
  103. if (!*data) throw unterminated_json_object();
  104. else if (*data != '}') throw json::malformed_json_exception{
  105. std::string("Unexpected non-terminated object '") + *data + "'"
  106. };
  107. else ++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{
  118. "Expecting '" + expected + "' polymorphic token, got '"
  119. + key + "' instead"
  120. };
  121. }
  122. if (json::helper::get_next_element(data) != ':') {
  123. throw malformed_object_association(*data);
  124. }
  125. }
  126. private:
  127. std::map<std::string, std::function<bool(Base const*)>> reverse_lookup;
  128. std::map<std::string, binder<Ptr>> mapping;
  129. };
  130. } }